Operating Systems Practical Examination Master Solutions (100 Marks Pattern)
Rigorous, exam-tested solutions for all 40 questions across Model Set 1 and Set 2. Every question displays the Official Question Paper Slip with both parts before providing the complete step-by-step solutions (Aim, System Specs, Numbered Algorithms, Math Models, C Programs, Shell Scripts, Tracing Tables, Output Verification, and Examiner Viva Voce Takeaways).
Model Practical Examination Paper 1 (Questions 1 β 20)
Each question contains Part (a) [50 Marks] and Part (b) [50 Marks] β Total 100 Marks.
QUESTION NO: 1 (MODEL SET 1)
SOLUTION FOR PART (a) [MAX MARKS: 50]
50 MarksExplain how the Deadlock Detection Algorithm differs when resources have multiple instances versus a single instance.
To analyze and compare the theoretical and structural differences between Deadlock Detection algorithms for single-instance resource models versus multi-instance resource systems.
OS Environment: Linux / Kali Linux 6.x (POSIX Compliant) | Compiler: GCC 12.2+ (Flags: -Wall -pthread) | Standard: ISO C11 / POSIX IEEE 1003.1.
- Step 1: Start the program and define Available resource vector, Allocation matrix, and Request matrix.
- Step 2: Initialize Work vector equal to Available vector: Work[j] = Available[j] for all resource types j.
- Step 3: Initialize Finish array: For all processes i, if Allocation[i][j] != 0 for any j, set Finish[i] = false; otherwise set Finish[i] = true.
- Step 4: Search for an index i such that Finish[i] == false and Request[i][j] <= Work[j] for all resource types j.
- Step 5: If such an index i is found, simulate resource reclamation: Work[j] = Work[j] + Allocation[i][j], set Finish[i] = true, and repeat Step 4.
- Step 6: If no such index i can be found and there exists any process with Finish[i] == false, declare that process i is in a DEADLOCKED state.
- Step 7: Print the identification numbers of all deadlocked processes and report system deadlock status, then terminate.
1. Single Instance of Each Resource Type:
β’ Uses a Wait-For Graph (WFG), obtained by collapsing resource nodes from the Resource Allocation Graph (RAG).
β’ A directed edge
Pi β Pj means process Pi is waiting for process Pj to release a resource.β’ Condition: A cycle in the WFG is a necessary and sufficient condition for deadlock (Cycle β Deadlock).
β’ Complexity: Cycle detection via Depth First Search (DFS) or Tarjan's algorithm takes
O(VΒ²) or O(V + E) time.2. Multiple Instances of Resource Types:
β’ Wait-for graphs cannot detect deadlocks because a cycle in a multi-instance graph does not imply deadlock (other instances may become available).
β’ Requires full matrix-based tracking: Allocation Matrix [nΓm], Request Matrix [nΓm], and Available Vector [m].
β’ Uses a reduction algorithm (Banker's style):
a)
Work = Available; For all i, if Allocation[i] != 0 then Finish[i] = false, else true.b) Find an index
i such that Finish[i] == false and Request[i] <= Work.c) If found:
Work = Work + Allocation[i]; Finish[i] = true; Repeat step b.d) If no such
i exists and some Finish[i] == false, then process Pi is deadlocked.β’ Complexity:
O(m Γ nΒ²) operations.
| Feature | Single-Instance Detection | Multiple-Instance Detection |
|---|---|---|
| Data Structure | Wait-For Graph (WFG) / Adjacency Matrix | Available [m], Allocation [nΓm], Request [nΓm] |
| Deadlock Criterion | Simple Cycle in Graph (Cycle β Deadlock) | Unmarked processes after Matrix Reduction |
| Cycle Sufficiency | Cycle is necessary AND sufficient | Cycle is necessary but NOT sufficient |
| Algorithm | DFS / Cycle Finding (Tarjan / Kosaraju) | Work-Finish Vector State Reduction |
| Time Complexity | O(nΒ²) where n is number of processes | O(m Γ nΒ²) where m = resources, n = processes |
| Implementation Overhead | Low space & runtime overhead | High overhead; periodic invocation needed |
Allocation Matrix [5x3], Request Matrix [5x3], Available Vector = [0, 0, 0]
/* C Concept Representation: Single-Instance (Cycle Finding) vs Multi-Instance */
#include <stdio.h>
#include <stdbool.h>
#define MAX_PROC 10
#define MAX_RES 10
// Multi-Instance Detection Engine
bool detectMultiInstanceDeadlock(int n, int m, int alloc[MAX_PROC][MAX_RES],
int req[MAX_PROC][MAX_RES], int avail[MAX_RES], int deadlocked[]) {
int work[MAX_RES];
bool finish[MAX_PROC];
int deadCount = 0;
for (int j = 0; j < m; j++) work[j] = avail[j];
for (int i = 0; i < n; i++) {
bool hasAlloc = false;
for (int j = 0; j < m; j++) if (alloc[i][j] != 0) { hasAlloc = true; break; }
finish[i] = !hasAlloc;
}
bool progress;
do {
progress = false;
for (int i = 0; i < n; i++) {
if (!finish[i]) {
bool canSatisfy = true;
for (int j = 0; j < m; j++) {
if (req[i][j] > work[j]) { canSatisfy = false; break; }
}
if (canSatisfy) {
for (int j = 0; j < m; j++) work[j] += alloc[i][j];
finish[i] = true;
progress = true;
}
}
}
} while (progress);
for (int i = 0; i < n; i++) {
if (!finish[i]) deadlocked[deadCount++] = i;
}
return deadCount > 0;
}
Ans: O(m Γ nΒ²), where m is the number of resource types and n is the number of processes in the system.
Ans: Because multiple resource instances allow other processes outside the cycle to release instances, which may satisfy a process inside the cycle and break the wait.
SOLUTION FOR PART (b) [MAX MARKS: 50]
50 MarksCompare how the shell script simulates deadlock detection output versus the actual matrix-based computation in C.
To demonstrate the architectural and computational contrast between high-level shell script process simulation and rigorous low-level matrix-based algorithmic computation in C.
Shell: GNU Bash 5.2+ (Bourne-Again Shell) | Execution Command: chmod +x script.sh && ./script.sh
- Step 1: Start the program and define Available resource vector, Allocation matrix, and Request matrix.
- Step 2: Initialize Work vector equal to Available vector: Work[j] = Available[j] for all resource types j.
- Step 3: Initialize Finish array: For all processes i, if Allocation[i][j] != 0 for any j, set Finish[i] = false; otherwise set Finish[i] = true.
- Step 4: Search for an index i such that Finish[i] == false and Request[i][j] <= Work[j] for all resource types j.
- Step 5: If such an index i is found, simulate resource reclamation: Work[j] = Work[j] + Allocation[i][j], set Finish[i] = true, and repeat Step 4.
- Step 6: If no such index i can be found and there exists any process with Finish[i] == false, declare that process i is in a DEADLOCKED state.
- Step 7: Print the identification numbers of all deadlocked processes and report system deadlock status, then terminate.
β’ Performs dynamic numeric matrix algebra on 2D arrays
Allocation[n][m] and Request[n][m].β’ Strictly evaluates whether vector inequalities
Request[i] β€ Work hold across all resource types.β’ Handles real operating system resource states, memory addresses, and execution safety invariants with mathematical precision.
2. Shell Script Simulation:
β’ High-level string manipulation, pipeline processing, and process status parsing (e.g., using
ps -eo state,pid or associative arrays).β’ Simulates state transitions by reading user-supplied lists of blocked process IDs or matching processes waiting indefinitely on named resources.
β’ Serves as an educational visualization and administrative monitoring tool rather than an in-kernel arbiter.
Allocation Matrix [5x3], Request Matrix [5x3], Available Vector = [0, 0, 0]
#!/bin/bash
# Shell Script simulating Deadlock Detection vs C Matrix Computation
echo "=================================================="
echo " SIMULATED SHELL SCRIPT DEADLOCK DETECTOR "
echo "=================================================="
# Processes and their requested locks
declare -A holds
declare -A requests
holds["P0"]="R1"
requests["P0"]="R2"
holds["P1"]="R2"
requests["P1"]="R3"
holds["P2"]="R3"
requests["P2"]="R1"
echo "Current Resource Allocation & Requests:"
for p in "${!holds[@]}"; do
echo " $p holds ${holds[$p]} and requests ${requests[$p]}"
done
# Detecting circular dependency in shell
echo -e "\nTracing Dependencies..."
visited=""
curr="P0"
deadlock=0
while [ -n "$curr" ]; do
visited="$visited $curr"
req_res=${requests[$curr]}
next_proc=""
for p in "${!holds[@]}"; do
if [ "${holds[$p]}" == "$req_res" ]; then
next_proc=$p
break
fi
done
echo " $curr waits for $req_res held by $next_proc"
if [[ "$visited" =~ "$next_proc" ]]; then
echo -e "\n>>> [ALERT] Circular wait detected involving: $visited $next_proc"
echo ">>> System is in DEADLOCK state!"
deadlock=1
break
fi
curr=$next_proc
done
if [ $deadlock -eq 0 ]; then
echo "No deadlock detected."
fi
Ans: O(m Γ nΒ²), where m is the number of resource types and n is the number of processes in the system.
Ans: Because multiple resource instances allow other processes outside the cycle to release instances, which may satisfy a process inside the cycle and break the wait.
QUESTION NO: 2 (MODEL SET 1)
SOLUTION FOR PART (a) [MAX MARKS: 50]
50 MarksWrite a C program to implement FIFO page replacement for the reference string 1 2 3 4 1 2 5 1 2 3 4 5 with 4 frames.
To implement the First-In-First-Out (FIFO) page replacement algorithm in C for the given 12-page reference string using 4 memory frames and compute total page faults and hits.
OS Environment: Linux / Kali Linux 6.x (POSIX Compliant) | Compiler: GCC 12.2+ (Flags: -Wall -pthread) | Standard: ISO C11 / POSIX IEEE 1003.1.
- Step 1: Start the program and declare variables for reference string, memory frames, page faults, hits, and victim frame pointer.
- Step 2: Initialize all frame slots with -1 to indicate that physical memory frames are currently empty.
- Step 3: Read each page reference from the reference string one by one in sequential arrival order.
- Step 4: Check if the incoming page is already present in any allocated frame slot. If found, mark as PAGE HIT and proceed to next reference.
- Step 5: If the page is not found (PAGE FAULT), check if free frames exist. If available, allocate the empty frame directly.
- Step 6: If all frames are occupied, apply FIFO replacement: replace the frame at index 'victim_ptr' and advance the circular pointer: victim_ptr = (victim_ptr + 1) % total_frames.
- Step 7: Increment page fault counter and display the intermediate contents of all memory frames after replacement.
- Step 8: Compute final metrics: Hit Ratio = (Hits / Total) * 100 and Fault Ratio = (Faults / Total) * 100, then terminate.
A circular pointer or FIFO queue maintains the index of the frame to replace:
β’ If page is present in any frame: PAGE HIT (no replacement).
β’ If page is absent and empty frame exists: Insert page into empty frame -> PAGE FAULT.
β’ If page is absent and all frames full: Replace page at
victim_index = (victim_index + 1) % num_frames -> PAGE FAULT.
Reference String = 1, 2, 3, 4, 1, 2, 5, 1, 2, 3, 4, 5 | Frame Capacity = 4 Frames
#include <stdio.h>
#define FRAMES 4
#define REF_LEN 12
int main() {
int ref_str[REF_LEN] = {1, 2, 3, 4, 1, 2, 5, 1, 2, 3, 4, 5};
int frames[FRAMES];
int victim = 0;
int page_faults = 0, page_hits = 0;
for (int i = 0; i < FRAMES; i++) frames[i] = -1;
printf("FIFO Page Replacement Simulation (Frames = %d)\n", FRAMES);
printf("Reference String: 1 2 3 4 1 2 5 1 2 3 4 5\n\n");
printf("%-5s | %-16s | %-10s\n", "Page", "Frame State", "Status");
printf("-----------------------------------------\n");
for (int i = 0; i < REF_LEN; i++) {
int page = ref_str[i];
int hit = 0;
for (int j = 0; j < FRAMES; j++) {
if (frames[j] == page) {
hit = 1;
page_hits++;
break;
}
}
if (!hit) {
frames[victim] = page;
victim = (victim + 1) % FRAMES;
page_faults++;
printf("%-5d | [", page);
for (int j = 0; j < FRAMES; j++) {
if (frames[j] != -1) printf(" %d", frames[j]);
else printf(" -");
}
printf(" ] | FAULT (F%d)\n", victim == 0 ? FRAMES : victim);
} else {
printf("%-5d | [", page);
for (int j = 0; j < FRAMES; j++) {
if (frames[j] != -1) printf(" %d", frames[j]);
else printf(" -");
}
printf(" ] | HIT\n");
}
}
printf("-----------------------------------------\n");
printf("Total References : %d\n", REF_LEN);
printf("Total Page Faults: %d\n", page_faults);
printf("Total Page Hits : %d\n", page_hits);
printf("Hit Ratio : %.2f%%\n", ((float)page_hits / REF_LEN) * 100);
printf("Fault Ratio : %.2f%%\n", ((float)page_faults / REF_LEN) * 100);
return 0;
}
Ans: Belady's Anomaly is the counter-intuitive phenomenon where increasing the number of physical frames increases the number of page faults. FIFO is subject to it; stack algorithms like LRU and Optimal are strictly immune.
Ans: Because it requires future knowledge of all upcoming memory page references, which cannot be known in advance in a general-purpose OS.
SOLUTION FOR PART (b) [MAX MARKS: 50]
50 MarksWrite a shell script to represent a reference string as an array and demonstrate FIFO frame replacement logic.
To construct a Bash shell script storing page references in an array and simulating FIFO buffer management across 4 frames.
Shell: GNU Bash 5.2+ (Bourne-Again Shell) | Execution Command: chmod +x script.sh && ./script.sh
- Step 1: Start the program and initialize operating system parameters.
- Step 2: Allocate required memory buffers and validate input parameters.
- Step 3: Execute the core algorithmic logic ensuring adherence to OS synchronization and scheduling standards.
- Step 4: Format and print console output tables and execution verification metrics.
- Step 5: Release system resources and terminate program execution cleanly.
pages=(1 2 3 4 1 2 5 1 2 3 4 5) and frames=(-1 -1 -1 -1).Using a modular pointer
victim=$(( (victim + 1) % 4 )), incoming pages overwrite the oldest occupied slot.
Standard POSIX test parameters and terminal arguments.
#!/bin/bash
# FIFO Page Replacement in Shell Script
pages=(1 2 3 4 1 2 5 1 2 3 4 5)
num_frames=4
frames=(-1 -1 -1 -1)
faults=0
hits=0
victim=0
echo "=== FIFO Page Replacement Simulation (Shell Script) ==="
echo "Reference Array: ${pages[*]}"
echo "Frame Slots : $num_frames"
echo ""
for page in "${pages[@]}"; do
hit=0
for ((j=0; j<num_frames; j++)); do
if [ ${frames[$j]} -eq $page ]; then
hit=1
break
fi
done
if [ $hit -eq 1 ]; then
hits=$((hits + 1))
echo "Page $page -> [${frames[*]}] : HIT"
else
frames[$victim]=$page
victim=$(( (victim + 1) % num_frames ))
faults=$((faults + 1))
echo "Page $page -> [${frames[*]}] : FAULT"
fi
done
echo ""
echo "Total Page Faults: $faults"
echo "Total Page Hits : $hits"
Ans: Belady's Anomaly is the counter-intuitive phenomenon where increasing the number of physical frames increases the number of page faults. FIFO is subject to it; stack algorithms like LRU and Optimal are strictly immune.
Ans: Because it requires future knowledge of all upcoming memory page references, which cannot be known in advance in a general-purpose OS.
QUESTION NO: 3 (MODEL SET 1)
SOLUTION FOR PART (a) [MAX MARKS: 50]
50 MarksExplain what happens when the page number exceeds the number of pages in the page table, with a C program check.
To demonstrate the MMU protection mechanism and write a C program that validates logical addresses against the Page Table Length Register (PTLR).
OS Environment: Linux / Kali Linux 6.x (POSIX Compliant) | Compiler: GCC 12.2+ (Flags: -Wall -pthread) | Standard: ISO C11 / POSIX IEEE 1003.1.
- Step 1: Start the program and configure Page Size (PS) and total number of pages in the process Page Table.
- Step 2: Read the Logical Address (LA) requested by the CPU.
- Step 3: Compute Page Number: page_no = LA / PS.
- Step 4: Compute Byte Offset: offset = LA % PS.
- Step 5: Check MMU bounds: If page_no >= Total_Pages, trigger internal Hardware Trap (Addressing Exception / Segmentation Fault SIGSEGV).
- Step 6: If page_no is valid, retrieve physical Frame Number from page table: frame_no = Page_Table[page_no].
- Step 7: Calculate Physical Address = (frame_no * PS) + offset and display translated physical address, then terminate.
β’ Logical address is split into Page Number (p) and Offset (d):
p = Logical_Address / Page_Size, d = Logical_Address % Page_Size.β’ The Memory Management Unit (MMU) compares
p against the Page Table Length Register (PTLR).β’ If
p >= PTLR (page table size), the address is outside the process's allocated virtual address space.β’ The CPU generates an Internal Hardware Trap (Addressing Exception / Segmentation Fault).
β’ The OS catches the trap and sends a
SIGSEGV signal to terminate the offending process.
Standard POSIX test parameters and terminal arguments.
#include <stdio.h>
#include <stdlib.h>
#define PAGE_SIZE 1024 // 1 KB
#define TOTAL_PAGES 4 // Legal pages: 0, 1, 2, 3
int page_table[TOTAL_PAGES] = {5, 8, 2, 9}; // Frame mapping
void translateAddress(unsigned int logical_addr) {
unsigned int page_no = logical_addr / PAGE_SIZE;
unsigned int offset = logical_addr % PAGE_SIZE;
printf("\n[Translating Logical Address: %u]\n", logical_addr);
printf("Calculated Page Number: %u | Offset: %u\n", page_no, offset);
// Hardware MMU Bound Check
if (page_no >= TOTAL_PAGES) {
printf(">>> [HARDWARE TRAP]: Page %u exceeds Page Table Limit (%d)!\n", page_no, TOTAL_PAGES);
printf(">>> TRAP TYPE : Addressing Exception / Segmentation Fault\n");
printf(">>> OS ACTION : Signal SIGSEGV sent. Process Aborted.\n");
} else {
unsigned int physical_addr = (page_table[page_no] * PAGE_SIZE) + offset;
printf(">>> Status: VALID (Mapped to Frame %d)\n", page_table[page_no]);
printf(">>> Physical Address = (%d * %d) + %u = %u\n",
page_table[page_no], PAGE_SIZE, offset, physical_addr);
}
}
int main() {
printf("=== Paging Bounds Checking Demonstration ===\n");
printf("Page Size = %d bytes | Valid Pages = 0 to %d\n", PAGE_SIZE, TOTAL_PAGES - 1);
translateAddress(2500); // Page 2, Offset 452 -> VALID
translateAddress(4095); // Page 3, Offset 1023 -> VALID
translateAddress(6500); // Page 6 -> EXCEEDS TOTAL PAGES!
return 0;
}
Ans: User Mode executes unprivileged user applications with limited hardware access. Kernel Mode executes privileged OS instructions with unrestricted access to memory and CPU registers.
SOLUTION FOR PART (b) [MAX MARKS: 50]
50 MarksWrite a shell script to display an 'Invalid Logical Address' message when the page number exceeds available pages.
To develop a Bash script calculating page number and offset from user input and throwing an explicit error on bounds violation.
Shell: GNU Bash 5.2+ (Bourne-Again Shell) | Execution Command: chmod +x script.sh && ./script.sh
- Step 1: Start the program and configure Page Size (PS) and total number of pages in the process Page Table.
- Step 2: Read the Logical Address (LA) requested by the CPU.
- Step 3: Compute Page Number: page_no = LA / PS.
- Step 4: Compute Byte Offset: offset = LA % PS.
- Step 5: Check MMU bounds: If page_no >= Total_Pages, trigger internal Hardware Trap (Addressing Exception / Segmentation Fault SIGSEGV).
- Step 6: If page_no is valid, retrieve physical Frame Number from page table: frame_no = Page_Table[page_no].
- Step 7: Calculate Physical Address = (frame_no * PS) + offset and display translated physical address, then terminate.
page=$(( logical_addr / page_size )) and evaluates [ $page -ge $max_pages ]. If true, prints red-flagged invalid address alert.
Logical Address = 2500 bytes (or 7250 bytes) | Page Size = 1024 bytes (1 KB)
#!/bin/bash
# Shell Script: Paging Address Boundary Validation
PAGE_SIZE=1024
MAX_PAGES=4
echo "-----------------------------------------------"
echo " LOGICAL ADDRESS TRANSLATION CHECKER "
echo "-----------------------------------------------"
echo "Page Size configured: $PAGE_SIZE bytes"
echo "Total Valid Pages : $MAX_PAGES (0 to $((MAX_PAGES-1)))"
echo ""
read -p "Enter Logical Address to Translate: " logical_addr
if ! [[ "$logical_addr" =~ ^[0-9]+$ ]]; then
echo "Error: Please enter a valid positive integer."
exit 1
fi
page_num=$(( logical_addr / PAGE_SIZE ))
offset=$(( logical_addr % PAGE_SIZE ))
echo "Derived Page Number : $page_num"
echo "Derived Byte Offset : $offset"
if [ $page_num -ge $MAX_PAGES ]; then
echo ""
echo "================================================="
echo " ERROR: Invalid Logical Address!"
echo " Reason: Page number ($page_num) exceeds allocated range (0-$((MAX_PAGES-1)))."
echo " Action: Segmentation Fault (SIGSEGV) Generated."
echo "================================================="
exit 1
else
echo "Status: Address is VALID. Translating to physical frame..."
fi
Ans: User Mode executes unprivileged user applications with limited hardware access. Kernel Mode executes privileged OS instructions with unrestricted access to memory and CPU registers.
QUESTION NO: 4 (MODEL SET 1)
SOLUTION FOR PART (a) [MAX MARKS: 50]
50 MarksExplain any five directory-related and file-related UNIX commands with syntax and examples.
To document essential directory and file manipulation commands in UNIX/Linux systems with syntax, options, and sample runs.
OS Environment: Linux / Kali Linux 6.x (POSIX Compliant) | Compiler: GCC 12.2+ (Flags: -Wall -pthread) | Standard: ISO C11 / POSIX IEEE 1003.1.
- Step 1: Start the program and initialize operating system parameters.
- Step 2: Allocate required memory buffers and validate input parameters.
- Step 3: Execute the core algorithmic logic ensuring adherence to OS synchronization and scheduling standards.
- Step 4: Format and print console output tables and execution verification metrics.
- Step 5: Release system resources and terminate program execution cleanly.
/.Standard directory management commands create, navigate, and remove directories.
File management commands handle creation, viewing, duplicating, moving, and deleting file entities.
Standard POSIX test parameters and terminal arguments.
/* Directory-Related Commands:
1. pwd (Print Working Directory)
Syntax: pwd [-L | -P]
Example: $ pwd
/home/student/oslab
2. mkdir (Make Directory)
Syntax: mkdir [options] <dirname>
Example: $ mkdir -p lab/ex4/code
3. cd (Change Directory)
Syntax: cd [directory_path]
Example: $ cd lab/ex4 && pwd
4. rmdir (Remove Empty Directory)
Syntax: rmdir [options] <dirname>
Example: $ rmdir temp_dir
5. ls (List Directory Contents)
Syntax: ls [options] [path]
Example: $ ls -la /var/log
File-Related Commands:
1. touch (Create Empty File / Update Timestamp)
Syntax: touch <filename>
Example: $ touch sample.txt
2. cat (Concatenate and Display Files)
Syntax: cat [options] [files]
Example: $ cat -n sample.txt
3. cp (Copy Files and Directories)
Syntax: cp [options] <source> <destination>
Example: $ cp -r src_folder/ dest_folder/
4. mv (Move or Rename Files)
Syntax: mv [options] <source> <destination>
Example: $ mv oldname.c newname.c
5. rm (Remove / Delete Files)
Syntax: rm [options] <filename>
Example: $ rm -f obsolete.log
*/
Ans: User Mode executes unprivileged user applications with limited hardware access. Kernel Mode executes privileged OS instructions with unrestricted access to memory and CPU registers.
SOLUTION FOR PART (b) [MAX MARKS: 50]
50 MarksWrite a shell script to find the greatest among three given numbers.
To develop an interactive Bash shell script utilizing conditional branching to determine the largest among three numerical inputs.
Shell: GNU Bash 5.2+ (Bourne-Again Shell) | Execution Command: chmod +x script.sh && ./script.sh
- Step 1: Start the program and initialize operating system parameters.
- Step 2: Allocate required memory buffers and validate input parameters.
- Step 3: Execute the core algorithmic logic ensuring adherence to OS synchronization and scheduling standards.
- Step 4: Format and print console output tables and execution verification metrics.
- Step 5: Release system resources and terminate program execution cleanly.
-gt (greater than) and -ge (greater than or equal to) or arithmetic compound evaluation (( a >= b && a >= c )).
Number A = 48, Number B = 95, Number C = 72
#!/bin/bash
# Shell script to find greatest among three numbers
echo "=== FIND GREATEST OF THREE NUMBERS ==="
read -p "Enter first number (A): " a
read -p "Enter second number (B): " b
read -p "Enter third number (C): " c
if [ $a -ge $b ] && [ $a -ge $c ]; then
greatest=$a
elif [ $b -ge $a ] && [ $b -ge $c ]; then
greatest=$b
else
greatest=$c
fi
echo ""
echo "Result: The greatest number among ($a, $b, $c) is: $greatest"
Ans: User Mode executes unprivileged user applications with limited hardware access. Kernel Mode executes privileged OS instructions with unrestricted access to memory and CPU registers.
QUESTION NO: 5 (MODEL SET 1)
SOLUTION FOR PART (a) [MAX MARKS: 50]
50 MarksExplain the concept of a page fault and how the frame size affects the number of page faults.
To elucidate the life cycle of a page fault, its performance impact (Effective Access Time), and how frame size and frame quantity influence page fault frequency.
OS Environment: Linux / Kali Linux 6.x (POSIX Compliant) | Compiler: GCC 12.2+ (Flags: -Wall -pthread) | Standard: ISO C11 / POSIX IEEE 1003.1.
- Step 1: Start the program and initialize operating system parameters.
- Step 2: Allocate required memory buffers and validate input parameters.
- Step 3: Execute the core algorithmic logic ensuring adherence to OS synchronization and scheduling standards.
- Step 4: Format and print console output tables and execution verification metrics.
- Step 5: Release system resources and terminate program execution cleanly.
β’ Occurs when a process accesses a page whose valid-invalid bit in the page table is marked '0' (Invalid / Not present in RAM).
β’ Service Sequence:
1) CPU detects invalid bit -> generates MMU trap to OS.
2) OS saves user registers and process state.
3) OS checks internal table: if invalid address -> abort (SIGSEGV); if valid -> page fault.
4) OS finds a free physical frame in RAM.
5) Schedules disk I/O to swap in the page from secondary backing store.
6) Once I/O completes, updates page table entry to Valid ('1') and records frame number.
7) Restarts the instruction that caused the trap.
2. Impact of Frame Allocation & Frame Size:
β’ Increasing Frame Count: More frames allow larger working sets to reside in memory, decreasing page fault frequency.
Anomaly: In FIFO, Belady's Anomaly can occur where increasing frame count from 3 to 4 increases page faults.
β’ Larger Page / Frame Size:
- Pros: Smaller page table size; transfers more contiguous code into RAM (high spatial locality).
- Cons: Higher internal fragmentation; larger swap-in transfer times.
Standard POSIX test parameters and terminal arguments.
/* Effective Access Time (EAT) Calculation Model */
// EAT = (1 - p) * Memory_Access_Time + p * Page_Fault_Service_Time
// If Memory Access = 100 ns, Page Fault Service = 10 ms (10,000,000 ns)
// If p = 0.001 (1 fault in 1000 references):
// EAT = 0.999 * 100 + 0.001 * 10,000,000 = 99.9 + 10,000 = 10,099.9 ns β 10 microseconds!
// (100 times slower due to disk swap latency)
Ans: User Mode executes unprivileged user applications with limited hardware access. Kernel Mode executes privileged OS instructions with unrestricted access to memory and CPU registers.
SOLUTION FOR PART (b) [MAX MARKS: 50]
50 MarksExplain why the Optimal page replacement algorithm produces the minimum number of page faults, with reference to the given reference string.
To demonstrate the mathematical optimality of Belady's Optimal algorithm (OPT/MIN) and compute page faults for string '1 2 3 4 1 2 5 1 2 3 4 5' with 4 frames.
Shell: GNU Bash 5.2+ (Bourne-Again Shell) | Execution Command: chmod +x script.sh && ./script.sh
- Step 1: Start the program and initialize operating system parameters.
- Step 2: Allocate required memory buffers and validate input parameters.
- Step 3: Execute the core algorithmic logic ensuring adherence to OS synchronization and scheduling standards.
- Step 4: Format and print console output tables and execution verification metrics.
- Step 5: Release system resources and terminate program execution cleanly.
Replace the page that will not be used for the longest period of time in the future.
Because it looks ahead into future references, it maximizes the distance to the next page fault at each replacement decision, provably achieving the lowest possible fault count.
| Ref | F1 | F2 | F3 | F4 | Status | Replacement Decision |
|---|---|---|---|---|---|---|
| 1 | 1 | - | - | - | FAULT | Empty frame allocated |
| 2 | 1 | 2 | - | - | FAULT | Empty frame allocated |
| 3 | 1 | 2 | 3 | - | FAULT | Empty frame allocated |
| 4 | 1 | 2 | 3 | 4 | FAULT | Empty frame allocated (Full) |
| 1 | 1 | 2 | 3 | 4 | HIT | Page 1 already present |
| 2 | 1 | 2 | 3 | 4 | HIT | Page 2 already present |
| 5 | 1 | 2 | 5 | 4 | FAULT | Replace 3 (next use: index 9 vs 4: index 10 vs 1: idx 7 vs 2: idx 8) -> Page 4 used furthest! |
| 1 | 1 | 2 | 5 | 4 | HIT | Page 1 present |
| 2 | 1 | 2 | 5 | 4 | HIT | Page 2 present |
| 3 | 1 | 2 | 3 | 4 | FAULT | Replace 5 (5 only used at very end index 11) |
| 4 | 1 | 2 | 3 | 4 | HIT | Page 4 present |
| 5 | 1 | 2 | 3 | 5 | FAULT | Replace 4 (no further use) |
Standard POSIX test parameters and terminal arguments.
Total References: 12
Total Faults in Optimal : 6 Page Faults
Total Hits in Optimal : 6 Page Hits
Comparison: FIFO produced 10 faults, whereas Optimal produces only 6 faults!
Ans: Belady's Anomaly is the counter-intuitive phenomenon where increasing the number of physical frames increases the number of page faults. FIFO is subject to it; stack algorithms like LRU and Optimal are strictly immune.
Ans: Because it requires future knowledge of all upcoming memory page references, which cannot be known in advance in a general-purpose OS.
QUESTION NO: 6 (MODEL SET 1)
SOLUTION FOR PART (a) [MAX MARKS: 50]
50 MarksExplain the CAT command and its various options with examples.
To detail the syntax, utility, and options of the UNIX concatenate (cat) command.
OS Environment: Linux / Kali Linux 6.x (POSIX Compliant) | Compiler: GCC 12.2+ (Flags: -Wall -pthread) | Standard: ISO C11 / POSIX IEEE 1003.1.
- Step 1: Start the program and initialize operating system parameters.
- Step 2: Allocate required memory buffers and validate input parameters.
- Step 3: Execute the core algorithmic logic ensuring adherence to OS synchronization and scheduling standards.
- Step 4: Format and print console output tables and execution verification metrics.
- Step 5: Release system resources and terminate program execution cleanly.
cat command reads files sequentially and writes them to standard output.Key options:
β’
-n: Number all output lines.β’
-b: Number non-empty output lines.β’
-s: Squeeze multiple adjacent blank lines into a single blank line.β’
-E: Display $ at end of each line.β’
-T: Display TAB characters as ^I.β’
-A: Equivalent to -vET (shows all non-printing characters).
Standard POSIX test parameters and terminal arguments.
/* Common cat command applications:
1. Create new file with standard input:
$ cat > notes.txt
Line 1
Line 2
(Ctrl+D)
2. Display file with line numbers:
$ cat -n notes.txt
3. Concatenate two files into a destination file:
$ cat part1.txt part2.txt > full_report.txt
4. Append file content:
$ cat appendix.txt >> full_report.txt
5. Suppress duplicate blank lines:
$ cat -s report.txt
*/
Ans: User Mode executes unprivileged user applications with limited hardware access. Kernel Mode executes privileged OS instructions with unrestricted access to memory and CPU registers.
SOLUTION FOR PART (b) [MAX MARKS: 50]
50 MarksWrite a shell script to find the sum of odd numbers up to N.
To develop a Bash script calculating the arithmetic sum of all odd integers in the interval [1, N].
Shell: GNU Bash 5.2+ (Bourne-Again Shell) | Execution Command: chmod +x script.sh && ./script.sh
- Step 1: Start the program and initialize operating system parameters.
- Step 2: Allocate required memory buffers and validate input parameters.
- Step 3: Execute the core algorithmic logic ensuring adherence to OS synchronization and scheduling standards.
- Step 4: Format and print console output tables and execution verification metrics.
- Step 5: Release system resources and terminate program execution cleanly.
i=1 to N with step 2 (or checking i % 2 != 0) and adds i to cumulative sum variable.
Upper Limit N = 15
#!/bin/bash
# Shell script to calculate sum of odd numbers up to N
echo "=== SUM OF ODD NUMBERS (1 to N) ==="
read -p "Enter upper limit N: " n
if [ $n -lt 1 ]; then
echo "Limit must be >= 1"
exit 1
fi
sum=0
echo -n "Odd numbers: "
for ((i=1; i<=n; i+=2)); do
echo -n "$i "
sum=$((sum + i))
done
echo ""
echo "Sum of all odd numbers up to $n = $sum"
Ans: User Mode executes unprivileged user applications with limited hardware access. Kernel Mode executes privileged OS instructions with unrestricted access to memory and CPU registers.
QUESTION NO: 7 (MODEL SET 1)
SOLUTION FOR PART (a) [MAX MARKS: 50]
50 MarksExplain HEAD, TAIL, MORE and GREP commands with syntax and examples.
To explain text inspection and search filters in UNIX.
OS Environment: Linux / Kali Linux 6.x (POSIX Compliant) | Compiler: GCC 12.2+ (Flags: -Wall -pthread) | Standard: ISO C11 / POSIX IEEE 1003.1.
- Step 1: Start the program and initialize operating system parameters.
- Step 2: Allocate required memory buffers and validate input parameters.
- Step 3: Execute the core algorithmic logic ensuring adherence to OS synchronization and scheduling standards.
- Step 4: Format and print console output tables and execution verification metrics.
- Step 5: Release system resources and terminate program execution cleanly.
Syntax:
head [-n lines] [file]. Example: head -n 5 /etc/passwd.2. TAIL: Outputs the last part of files (default: 10 lines).
Syntax:
tail [-n lines] [-f] [file]. -f follows live append stream (e.g., logs).3. MORE: Terminal pager for viewing text screen by screen.
Syntax:
more [options] [file]. (Space: next page, Enter: next line, Q: quit).4. GREP (Global Regular Expression Print): Searches for patterns matching regular expressions.
Syntax:
grep [options] "pattern" [file].Options:
-i (ignore case), -v (invert match), -c (count matches), -n (print line numbers).
Standard POSIX test parameters and terminal arguments.
/* Practical Examples: */
// View first 3 lines of system users
$ head -n 3 /etc/passwd
// Monitor real-time authentication logs
$ tail -f /var/log/auth.log
// Search case-insensitively with line numbers
$ grep -in "error" /var/log/syslog
Ans: User Mode executes unprivileged user applications with limited hardware access. Kernel Mode executes privileged OS instructions with unrestricted access to memory and CPU registers.
SOLUTION FOR PART (b) [MAX MARKS: 50]
50 MarksWrite a shell script to generate the Fibonacci series up to a given limit.
To generate Fibonacci numbers F(n) = F(n-1) + F(n-2) starting from 0, 1 up to value limit N.
Shell: GNU Bash 5.2+ (Bourne-Again Shell) | Execution Command: chmod +x script.sh && ./script.sh
- Step 1: Start the program and initialize operating system parameters.
- Step 2: Allocate required memory buffers and validate input parameters.
- Step 3: Execute the core algorithmic logic ensuring adherence to OS synchronization and scheduling standards.
- Step 4: Format and print console output tables and execution verification metrics.
- Step 5: Release system resources and terminate program execution cleanly.
a=0 and b=1. In each iteration, prints a, computes next = a + b, shifts a = b and b = next while a <= N.
Upper Limit Limit N = 50
#!/bin/bash
# Fibonacci series generation up to limit N
echo "=== FIBONACCI SERIES GENERATOR ==="
read -p "Enter limit N: " limit
a=0
b=1
echo -n "Fibonacci Series up to $limit: "
while [ $a -le $limit ]; do
echo -n "$a "
fn=$((a + b))
a=$b
b=$fn
done
echo ""
Ans: User Mode executes unprivileged user applications with limited hardware access. Kernel Mode executes privileged OS instructions with unrestricted access to memory and CPU registers.
QUESTION NO: 8 (MODEL SET 1)
SOLUTION FOR PART (a) [MAX MARKS: 50]
50 MarksWrite a C program to create a child process using fork() and display the process IDs of parent and child using getpid() and getppid().
To demonstrate process creation via fork() and inspect Process IDs (PID) and Parent Process IDs (PPID).
OS Environment: Linux / Kali Linux 6.x (POSIX Compliant) | Compiler: GCC 12.2+ (Flags: -Wall -pthread) | Standard: ISO C11 / POSIX IEEE 1003.1.
- Step 1: Start the program and declare a process identifier variable of type pid_t.
- Step 2: Call the fork() system call to create a child process duplicating the calling process context.
- Step 3: Check return value: If pid < 0, print fork failure message and exit with status 1.
- Step 4: If pid == 0 (Child context): Execute child-specific instructions and display child's PID using getpid() and parent's PID using getppid().
- Step 5: If pid > 0 (Parent context): Display parent's PID using getpid() and spawned child PID. Invoke wait(NULL) to prevent zombie creation.
- Step 6: Cleanly terminate both parent and child execution paths.
fork() duplicates the calling process.β’ Return value
< 0: Error creating child.β’ Return value
== 0: Child process context. getpid() returns child PID; getppid() returns parent PID.β’ Return value
> 0: Parent process context. Return value is child's PID. getpid() returns parent PID.
Standard POSIX test parameters and terminal arguments.
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main() {
pid_t pid;
printf("Parent Process before fork() [PID: %d]\n\n", getpid());
pid = fork();
if (pid < 0) {
perror("Fork failed");
return 1;
} else if (pid == 0) {
// Child execution path
printf("[CHILD PROCESS]\n");
printf(" My PID : %d\n", getpid());
printf(" My Parent's PPID : %d\n", getppid());
} else {
// Parent execution path
wait(NULL); // Wait for child completion
printf("\n[PARENT PROCESS]\n");
printf(" My PID : %d\n", getpid());
printf(" Created Child PID: %d\n", pid);
}
return 0;
}
Ans: fork() returns -1 on error, 0 to the newly created child process, and the child's positive PID to the parent process.
Ans: An Orphan process is a running child whose parent has terminated (adopted by init/systemd). A Zombie process is a terminated process whose exit status has not yet been read by its parent via wait().
SOLUTION FOR PART (b) [MAX MARKS: 50]
50 MarksWrite a shell script to create a background child process and display its process ID and parent process ID.
To launch an asynchronous subshell with & and inspect special shell parameters $! and $.
Shell: GNU Bash 5.2+ (Bourne-Again Shell) | Execution Command: chmod +x script.sh && ./script.sh
- Step 1: Start the program and initialize operating system parameters.
- Step 2: Allocate required memory buffers and validate input parameters.
- Step 3: Execute the core algorithmic logic ensuring adherence to OS synchronization and scheduling standards.
- Step 4: Format and print console output tables and execution verification metrics.
- Step 5: Release system resources and terminate program execution cleanly.
$ gives the PID of the current shell (parent).$! contains the PID of the most recently executed background command.
Standard POSIX test parameters and terminal arguments.
#!/bin/bash
# Shell Script: Background Child Process & PID Display
echo "Parent Shell Running [PID: $]"
# Launch background subshell
(
echo " [Child Subshell] Started in Background"
echo " [Child Subshell] Child PID : $BASHPID"
echo " [Child Subshell] Parent PPID: $PPID"
sleep 2
echo " [Child Subshell] Finished Task."
) &
child_pid=$!
echo "Parent created Child with PID: $child_pid"
echo "Parent waiting for child..."
wait $child_pid
echo "Parent successfully synchronized with child."
Ans: fork() returns -1 on error, 0 to the newly created child process, and the child's positive PID to the parent process.
Ans: An Orphan process is a running child whose parent has terminated (adopted by init/systemd). A Zombie process is a terminated process whose exit status has not yet been read by its parent via wait().
QUESTION NO: 9 (MODEL SET 1)
SOLUTION FOR PART (a) [MAX MARKS: 50]
50 MarksWrite a C program to demonstrate process synchronization between parent and child using the wait() system call.
To prevent zombie processes and demonstrate deterministic execution ordering between parent and child via wait().
OS Environment: Linux / Kali Linux 6.x (POSIX Compliant) | Compiler: GCC 12.2+ (Flags: -Wall -pthread) | Standard: ISO C11 / POSIX IEEE 1003.1.
- Step 1: Start the program and initialize operating system parameters.
- Step 2: Allocate required memory buffers and validate input parameters.
- Step 3: Execute the core algorithmic logic ensuring adherence to OS synchronization and scheduling standards.
- Step 4: Format and print console output tables and execution verification metrics.
- Step 5: Release system resources and terminate program execution cleanly.
pid_t wait(int *status) suspends the calling parent process until one of its children terminates.Inspect exit status with macros:
β’
WIFEXITED(status): Evaluates to true if child terminated normally.β’
WEXITSTATUS(status): Returns child's 8-bit exit code passed to exit().
Standard POSIX test parameters and terminal arguments.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
int main() {
pid_t pid = fork();
if (pid < 0) {
perror("Fork failed");
exit(1);
} else if (pid == 0) {
printf("Child (PID: %d) executing work...\n", getpid());
sleep(2); // Simulate task
printf("Child task finished. Exiting with code 42.\n");
exit(42);
} else {
int status;
printf("Parent (PID: %d) waiting for Child (PID: %d)...\n", getpid(), pid);
pid_t dead_child = wait(&status);
if (WIFEXITED(status)) {
printf("Parent: Child %d terminated normally with exit code %d.\n",
dead_child, WEXITSTATUS(status));
}
printf("Parent resuming and terminating.\n");
}
return 0;
}
Ans: fork() returns -1 on error, 0 to the newly created child process, and the child's positive PID to the parent process.
Ans: An Orphan process is a running child whose parent has terminated (adopted by init/systemd). A Zombie process is a terminated process whose exit status has not yet been read by its parent via wait().
SOLUTION FOR PART (b) [MAX MARKS: 50]
50 MarksWrite a shell script to demonstrate parent-child process synchronization using the wait command.
To demonstrate barrier synchronization in shell scripting using the built-in wait command.
Shell: GNU Bash 5.2+ (Bourne-Again Shell) | Execution Command: chmod +x script.sh && ./script.sh
- Step 1: Start the program and initialize operating system parameters.
- Step 2: Allocate required memory buffers and validate input parameters.
- Step 3: Execute the core algorithmic logic ensuring adherence to OS synchronization and scheduling standards.
- Step 4: Format and print console output tables and execution verification metrics.
- Step 5: Release system resources and terminate program execution cleanly.
& execute concurrently.The
wait [pid] command blocks the parent script until specified background processes finish.
Standard POSIX test parameters and terminal arguments.
#!/bin/bash
# Parent-Child Synchronization in Shell
echo "Parent process $ starting worker tasks..."
# Spawn worker 1
( sleep 2; echo " Worker 1 (PID: $BASHPID) finished data fetching." ) &
pid1=$!
# Spawn worker 2
( sleep 3; echo " Worker 2 (PID: $BASHPID) finished index building." ) &
pid2=$!
echo "Parent waiting for Worker 1 ($pid1) and Worker 2 ($pid2) to complete..."
wait $pid1
echo "Parent: Worker 1 finished. Still awaiting Worker 2..."
wait $pid2
echo "Parent: All background child tasks synchronized. Generating final report."
Ans: fork() returns -1 on error, 0 to the newly created child process, and the child's positive PID to the parent process.
Ans: An Orphan process is a running child whose parent has terminated (adopted by init/systemd). A Zombie process is a terminated process whose exit status has not yet been read by its parent via wait().
QUESTION NO: 10 (MODEL SET 1)
SOLUTION FOR PART (a) [MAX MARKS: 50]
50 MarksWrite a C program to open a file and close it using the close() system call.
To demonstrate low-level file I/O operations using open(), read(), write(), and close() system calls.
OS Environment: Linux / Kali Linux 6.x (POSIX Compliant) | Compiler: GCC 12.2+ (Flags: -Wall -pthread) | Standard: ISO C11 / POSIX IEEE 1003.1.
- Step 1: Start the program and initialize operating system parameters.
- Step 2: Allocate required memory buffers and validate input parameters.
- Step 3: Execute the core algorithmic logic ensuring adherence to OS synchronization and scheduling standards.
- Step 4: Format and print console output tables and execution verification metrics.
- Step 5: Release system resources and terminate program execution cleanly.
int open(const char *pathname, int flags, mode_t mode) returns a non-negative integer file descriptor (FD).β’
int close(int fd) frees the file descriptor entry from the process file table.
Standard POSIX test parameters and terminal arguments.
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
int main() {
int fd;
char buffer[64];
ssize_t bytes_written, bytes_read;
// Open file for read/write, create if not present
fd = open("exam_test.txt", O_CREAT | O_RDWR | O_TRUNC, 0644);
if (fd < 0) {
perror("Failed to open file");
exit(1);
}
printf("File opened successfully with File Descriptor: %d\n", fd);
// Write content
bytes_written = write(fd, "OS Lab Exam 2026\n", 17);
printf("Written %zd bytes to file.\n", bytes_written);
// Reposition cursor to beginning
lseek(fd, 0, SEEK_SET);
// Read back
bytes_read = read(fd, buffer, sizeof(buffer) - 1);
buffer[bytes_read] = '\0';
printf("Read from file: %s", buffer);
// Close file descriptor
if (close(fd) == 0) {
printf("File descriptor %d closed successfully.\n", fd);
} else {
perror("Close failed");
}
return 0;
}
Ans: User Mode executes unprivileged user applications with limited hardware access. Kernel Mode executes privileged OS instructions with unrestricted access to memory and CPU registers.
SOLUTION FOR PART (b) [MAX MARKS: 50]
50 MarksWrite a shell script to open and close a file using exec and file descriptor redirection.
To manage custom file descriptors (FD 3) in Bash using the exec command for persistent stream reading and clean descriptor closure.
Shell: GNU Bash 5.2+ (Bourne-Again Shell) | Execution Command: chmod +x script.sh && ./script.sh
- Step 1: Start the program and initialize operating system parameters.
- Step 2: Allocate required memory buffers and validate input parameters.
- Step 3: Execute the core algorithmic logic ensuring adherence to OS synchronization and scheduling standards.
- Step 4: Format and print console output tables and execution verification metrics.
- Step 5: Release system resources and terminate program execution cleanly.
exec 3< filename binds FD 3 to the file for reading.read -u 3 variable consumes lines from FD 3.exec 3<&- closes custom file descriptor 3.
Standard POSIX test parameters and terminal arguments.
#!/bin/bash
# Shell Script: File Descriptor manipulation using exec
TEST_FILE="fd_sample.txt"
echo -e "Line 1: Operating Systems\nLine 2: System Calls\nLine 3: Process Sync" > $TEST_FILE
echo "Opening '$TEST_FILE' on custom File Descriptor 3..."
exec 3< $TEST_FILE
echo "Reading file line by line through FD 3:"
line_num=1
while read -u 3 line; do
echo " [FD 3 Line $line_num]: $line"
line_num=$((line_num + 1))
done
echo "Closing File Descriptor 3..."
exec 3<&-
echo "Verification: Checking FD 3 closure..."
read -u 3 test_line 2>/dev/null
if [ $? -ne 0 ]; then
echo "Success: File Descriptor 3 is cleanly closed."
fi
rm -f $TEST_FILE
Ans: User Mode executes unprivileged user applications with limited hardware access. Kernel Mode executes privileged OS instructions with unrestricted access to memory and CPU registers.
QUESTION NO: 11 (MODEL SET 1)
SOLUTION FOR PART (a) [MAX MARKS: 50]
50 MarksExplain the fork() system call with its syntax and write a C program to illustrate process creation.
To explain the mechanics of process address space duplication, Copy-On-Write (COW), and write an illustrative C program.
OS Environment: Linux / Kali Linux 6.x (POSIX Compliant) | Compiler: GCC 12.2+ (Flags: -Wall -pthread) | Standard: ISO C11 / POSIX IEEE 1003.1.
- Step 1: Start the program and declare a process identifier variable of type pid_t.
- Step 2: Call the fork() system call to create a child process duplicating the calling process context.
- Step 3: Check return value: If pid < 0, print fork failure message and exit with status 1.
- Step 4: If pid == 0 (Child context): Execute child-specific instructions and display child's PID using getpid() and parent's PID using getppid().
- Step 5: If pid > 0 (Parent context): Display parent's PID using getpid() and spawned child PID. Invoke wait(NULL) to prevent zombie creation.
- Step 6: Cleanly terminate both parent and child execution paths.
pid_t fork(void); creates a new process by duplicating the calling process:β’ Exact duplicate of memory segments (Text, Data, Heap, Stack). Modern kernels use Copy-On-Write (COW).
β’ Child receives a unique Process ID (PID).
β’ Open file descriptors are shared between parent and child.
β’ Returns:
-
-1: Creation failure (Resource exhaustion).-
0: Returned to newly created child process.-
PID > 0: Child's PID returned to parent.
Standard POSIX test parameters and terminal arguments.
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main() {
int shared_var = 100;
pid_t pid = fork();
if (pid < 0) {
fprintf(stderr, "Fork failed!\n");
return 1;
} else if (pid == 0) {
shared_var += 50;
printf("[CHILD PROCESS] PID: %d, PPID: %d | Local shared_var = %d\n",
getpid(), getppid(), shared_var);
} else {
printf("[PARENT PROCESS] PID: %d, Child PID: %d | Local shared_var = %d\n",
getpid(), pid, shared_var);
}
return 0;
}
Ans: fork() returns -1 on error, 0 to the newly created child process, and the child's positive PID to the parent process.
Ans: An Orphan process is a running child whose parent has terminated (adopted by init/systemd). A Zombie process is a terminated process whose exit status has not yet been read by its parent via wait().
SOLUTION FOR PART (b) [MAX MARKS: 50]
50 MarksExplain how process creation is simulated in shell scripting and write a shell script to demonstrate it.
To illustrate subshell spawning, variable isolation, and concurrent process simulation in Bash.
Shell: GNU Bash 5.2+ (Bourne-Again Shell) | Execution Command: chmod +x script.sh && ./script.sh
- Step 1: Start the program and declare a process identifier variable of type pid_t.
- Step 2: Call the fork() system call to create a child process duplicating the calling process context.
- Step 3: Check return value: If pid < 0, print fork failure message and exit with status 1.
- Step 4: If pid == 0 (Child context): Execute child-specific instructions and display child's PID using getpid() and parent's PID using getppid().
- Step 5: If pid > 0 (Parent context): Display parent's PID using getpid() and spawned child PID. Invoke wait(NULL) to prevent zombie creation.
- Step 6: Cleanly terminate both parent and child execution paths.
( command ) or executed in background with &, the shell invokes internal fork() and exec() system calls, creating a subshell environment with copied environment variables but isolated write state.
Standard POSIX test parameters and terminal arguments.
#!/bin/bash
# Process creation simulation in Shell
MAIN_VAR="Original Parent Value"
echo "Parent PID: $ | MAIN_VAR = '$MAIN_VAR'"
echo "Spawning subshell (simulating fork)..."
(
# Inside child subshell
echo " [Child Subshell] PID: $BASHPID, PPID: $PPID"
MAIN_VAR="Modified by Child"
echo " [Child Subshell] MAIN_VAR inside child = '$MAIN_VAR'"
)
echo "Back in Parent PID: $ | MAIN_VAR = '$MAIN_VAR'"
echo "Notice: Parent memory remained unaltered (Memory Isolation proven)."
Ans: fork() returns -1 on error, 0 to the newly created child process, and the child's positive PID to the parent process.
Ans: An Orphan process is a running child whose parent has terminated (adopted by init/systemd). A Zombie process is a terminated process whose exit status has not yet been read by its parent via wait().
QUESTION NO: 12 (MODEL SET 1)
SOLUTION FOR PART (a) [MAX MARKS: 50]
50 MarksWrite a C program to implement the FCFS CPU scheduling algorithm and calculate average waiting time and turnaround time.
To implement non-preemptive First-Come First-Served (FCFS) CPU scheduling and compute metrics for N processes.
OS Environment: Linux / Kali Linux 6.x (POSIX Compliant) | Compiler: GCC 12.2+ (Flags: -Wall -pthread) | Standard: ISO C11 / POSIX IEEE 1003.1.
- Step 1: Start the program and input the number of processes and their corresponding CPU Burst Times (BT).
- Step 2: Set Waiting Time of the first arriving process to zero: WT[0] = 0, and Turnaround Time TAT[0] = BT[0].
- Step 3: For each subsequent process i from 1 to n-1, calculate Waiting Time: WT[i] = WT[i-1] + BT[i-1].
- Step 4: Compute Turnaround Time for each process: TAT[i] = WT[i] + BT[i].
- Step 5: Accumulate total Waiting Time and total Turnaround Time across all processes.
- Step 6: Calculate Average Waiting Time = Total_WT / n and Average Turnaround Time = Total_TAT / n.
- Step 7: Print formatted table showing Process ID, Burst Time, Waiting Time, and Turnaround Time, then terminate.
β’
Completion Time (CT) = CT_prev + Burst Time (BT)β’
Turnaround Time (TAT) = Completion Time - Arrival Timeβ’
Waiting Time (WT) = Turnaround Time - Burst Time
Standard POSIX test parameters and terminal arguments.
#include <stdio.h>
int main() {
int n = 4;
int bt[] = {5, 3, 8, 6};
int wt[4], tat[4];
float total_wt = 0, total_tat = 0;
wt[0] = 0; // First process waits 0 ms
tat[0] = bt[0];
for (int i = 1; i < n; i++) {
wt[i] = wt[i - 1] + bt[i - 1];
tat[i] = wt[i] + bt[i];
}
printf("=== FCFS CPU SCHEDULING (C Program) ===\n");
printf("%-8s | %-10s | %-12s | %-15s\n", "Process", "Burst Time", "Waiting Time", "Turnaround Time");
printf("---------------------------------------------------\n");
for (int i = 0; i < n; i++) {
total_wt += wt[i];
total_tat += tat[i];
printf("P%-7d | %-10d | %-12d | %-15d\n", i + 1, bt[i], wt[i], tat[i]);
}
printf("---------------------------------------------------\n");
printf("Average Waiting Time : %.2f ms\n", total_wt / n);
printf("Average Turnaround Time : %.2f ms\n", total_tat / n);
return 0;
}
Ans: The Convoy Effect occurs when numerous short I/O-bound processes are blocked waiting behind a single long CPU-bound process, causing high average waiting time.
Ans: Too large: RR degenerates into FCFS. Too small: CPU spends excessive time performing context switches instead of executing user code.
SOLUTION FOR PART (b) [MAX MARKS: 50]
50 MarksWrite a shell script to implement the FCFS CPU scheduling algorithm and display waiting time and turnaround time.
To implement FCFS CPU scheduling in Bash using arrays.
Shell: GNU Bash 5.2+ (Bourne-Again Shell) | Execution Command: chmod +x script.sh && ./script.sh
- Step 1: Start the program and input the number of processes and their corresponding CPU Burst Times (BT).
- Step 2: Set Waiting Time of the first arriving process to zero: WT[0] = 0, and Turnaround Time TAT[0] = BT[0].
- Step 3: For each subsequent process i from 1 to n-1, calculate Waiting Time: WT[i] = WT[i-1] + BT[i-1].
- Step 4: Compute Turnaround Time for each process: TAT[i] = WT[i] + BT[i].
- Step 5: Accumulate total Waiting Time and total Turnaround Time across all processes.
- Step 6: Calculate Average Waiting Time = Total_WT / n and Average Turnaround Time = Total_TAT / n.
- Step 7: Print formatted table showing Process ID, Burst Time, Waiting Time, and Turnaround Time, then terminate.
Standard POSIX test parameters and terminal arguments.
#!/bin/bash
# Shell Script: FCFS CPU Scheduling
bt=(5 3 8 6)
n=${#bt[@]}
wt=(0)
tat=(${bt[0]})
total_wt=0
total_tat=${bt[0]}
for ((i=1; i<n; i++)); do
wt[$i]=$(( wt[i-1] + bt[i-1] ))
tat[$i]=$(( wt[i] + bt[i] ))
total_wt=$(( total_wt + wt[i] ))
total_tat=$(( total_tat + tat[i] ))
done
echo "=== FCFS CPU SCHEDULING (Shell Script) ==="
printf "%-8s %-10s %-12s %-15s\n" "Process" "Burst Time" "Waiting Time" "Turnaround Time"
echo "---------------------------------------------------"
for ((i=0; i<n; i++)); do
printf "P%-7d %-10d %-12d %-15d\n" $((i+1)) ${bt[$i]} ${wt[$i]} ${tat[$i]}
done
echo "---------------------------------------------------"
avg_wt=$(echo "scale=2; $total_wt / $n" | bc -l 2>/dev/null || awk "BEGIN {printf \"%.2f\", $total_wt / $n}")
avg_tat=$(echo "scale=2; $total_tat / $n" | bc -l 2>/dev/null || awk "BEGIN {printf \"%.2f\", $total_tat / $n}")
echo "Average Waiting Time : $avg_wt ms"
echo "Average Turnaround Time : $avg_tat ms"
Ans: The Convoy Effect occurs when numerous short I/O-bound processes are blocked waiting behind a single long CPU-bound process, causing high average waiting time.
Ans: Too large: RR degenerates into FCFS. Too small: CPU spends excessive time performing context switches instead of executing user code.
QUESTION NO: 13 (MODEL SET 1)
SOLUTION FOR PART (a) [MAX MARKS: 50]
50 MarksWrite a C program to implement Non-Preemptive Shortest Job First (SJF) scheduling algorithm.
To sort ready processes in ascending order of Burst Time to minimize average waiting time.
OS Environment: Linux / Kali Linux 6.x (POSIX Compliant) | Compiler: GCC 12.2+ (Flags: -Wall -pthread) | Standard: ISO C11 / POSIX IEEE 1003.1.
- Step 1: Start the program and input the number of processes along with their CPU Burst Times.
- Step 2: Sort the processes in ascending order of their Burst Times using standard bubble sort.
- Step 3: Assign Waiting Time of the shortest job to zero: WT[0] = 0.
- Step 4: Iteratively compute Waiting Time for remaining jobs: WT[i] = WT[i-1] + BT[i-1].
- Step 5: Compute Turnaround Time for each job: TAT[i] = WT[i] + BT[i].
- Step 6: Calculate Average Waiting Time and Average Turnaround Time.
- Step 7: Print the sorted execution sequence, process metrics, and averages, then terminate.
Sorting burst times:
bt[j] > bt[j+1] -> swap burst times and process IDs.
Standard POSIX test parameters and terminal arguments.
#include <stdio.h>
int main() {
int n = 4;
int p[] = {1, 2, 3, 4};
int bt[] = {6, 8, 3, 4};
int wt[4], tat[4];
float total_wt = 0, total_tat = 0;
// Sort by Burst Time (Bubble Sort)
for (int i = 0; i < n - 1; i++) {
for (int j = 0; j < n - i - 1; j++) {
if (bt[j] > bt[j + 1]) {
int temp = bt[j]; bt[j] = bt[j + 1]; bt[j + 1] = temp;
temp = p[j]; p[j] = p[j + 1]; p[j + 1] = temp;
}
}
}
wt[0] = 0;
tat[0] = bt[0];
for (int i = 1; i < n; i++) {
wt[i] = wt[i - 1] + bt[i - 1];
tat[i] = wt[i] + bt[i];
}
printf("=== SJF NON-PREEMPTIVE CPU SCHEDULING ===\n");
printf("%-8s | %-10s | %-12s | %-15s\n", "Process", "Burst Time", "Waiting Time", "Turnaround Time");
printf("---------------------------------------------------\n");
for (int i = 0; i < n; i++) {
total_wt += wt[i]; total_tat += tat[i];
printf("P%-7d | %-10d | %-12d | %-15d\n", p[i], bt[i], wt[i], tat[i]);
}
printf("---------------------------------------------------\n");
printf("Average Waiting Time : %.2f ms\n", total_wt / n);
printf("Average Turnaround Time : %.2f ms\n", total_tat / n);
return 0;
}
Ans: The Convoy Effect occurs when numerous short I/O-bound processes are blocked waiting behind a single long CPU-bound process, causing high average waiting time.
Ans: Too large: RR degenerates into FCFS. Too small: CPU spends excessive time performing context switches instead of executing user code.
SOLUTION FOR PART (b) [MAX MARKS: 50]
50 MarksWrite a shell script to implement Non-Preemptive SJF scheduling algorithm using array sorting.
To demonstrate bubble sort in Bash to reorder processes by burst time and compute waiting time.
Shell: GNU Bash 5.2+ (Bourne-Again Shell) | Execution Command: chmod +x script.sh && ./script.sh
- Step 1: Start the program and input the number of processes along with their CPU Burst Times.
- Step 2: Sort the processes in ascending order of their Burst Times using standard bubble sort.
- Step 3: Assign Waiting Time of the shortest job to zero: WT[0] = 0.
- Step 4: Iteratively compute Waiting Time for remaining jobs: WT[i] = WT[i-1] + BT[i-1].
- Step 5: Compute Turnaround Time for each job: TAT[i] = WT[i] + BT[i].
- Step 6: Calculate Average Waiting Time and Average Turnaround Time.
- Step 7: Print the sorted execution sequence, process metrics, and averages, then terminate.
proc and bt before applying cumulative FCFS wait calculation.
Standard POSIX test parameters and terminal arguments.
#!/bin/bash
# Shell Script: Non-Preemptive SJF via Array Sorting
proc=(1 2 3 4)
bt=(6 8 3 4)
n=${#bt[@]}
# Bubble Sort on burst times
for ((i=0; i<n-1; i++)); do
for ((j=0; j<n-i-1; j++)); do
if [ ${bt[j]} -gt ${bt[j+1]} ]; then
# Swap burst times
tmp=${bt[j]}; bt[j]=${bt[j+1]}; bt[j+1]=$tmp
# Swap process ids
tmp_p=${proc[j]}; proc[j]=${proc[j+1]}; proc[j+1]=$tmp_p
fi
done
done
wt=(0)
tat=(${bt[0]})
total_wt=0
total_tat=${bt[0]}
for ((i=1; i<n; i++)); do
wt[$i]=$(( wt[i-1] + bt[i-1] ))
tat[$i]=$(( wt[i] + bt[i] ))
total_wt=$(( total_wt + wt[i] ))
total_tat=$(( total_tat + tat[i] ))
done
echo "=== SJF SCHEDULING (Shell Script) ==="
printf "%-8s %-10s %-12s %-15s\n" "Process" "Burst Time" "Waiting Time" "Turnaround Time"
echo "---------------------------------------------------"
for ((i=0; i<n; i++)); do
printf "P%-7d %-10d %-12d %-15d\n" ${proc[$i]} ${bt[$i]} ${wt[$i]} ${tat[$i]}
done
echo "---------------------------------------------------"
echo "Average Waiting Time : $(awk "BEGIN {printf \"%.2f\", $total_wt / $n}") ms"
echo "Average Turnaround Time : $(awk "BEGIN {printf \"%.2f\", $total_tat / $n}") ms"
Ans: The Convoy Effect occurs when numerous short I/O-bound processes are blocked waiting behind a single long CPU-bound process, causing high average waiting time.
Ans: Too large: RR degenerates into FCFS. Too small: CPU spends excessive time performing context switches instead of executing user code.
QUESTION NO: 14 (MODEL SET 1)
SOLUTION FOR PART (a) [MAX MARKS: 50]
50 MarksWrite a C program to implement Priority Scheduling algorithm and calculate waiting time and turnaround time.
To implement non-preemptive Priority CPU scheduling where CPU is allocated to the highest priority process (lower integer = higher priority).
OS Environment: Linux / Kali Linux 6.x (POSIX Compliant) | Compiler: GCC 12.2+ (Flags: -Wall -pthread) | Standard: ISO C11 / POSIX IEEE 1003.1.
- Step 1: Start the program and read the number of processes, Burst Times, and Priority integer values.
- Step 2: Sort the processes in ascending order of Priority values (lower number represents higher priority).
- Step 3: Set Waiting Time for the highest priority process to 0: WT[0] = 0.
- Step 4: Calculate Waiting Time for subsequent processes: WT[i] = WT[i-1] + BT[i-1].
- Step 5: Calculate Turnaround Time for each process: TAT[i] = WT[i] + BT[i].
- Step 6: Compute Average Waiting Time and Average Turnaround Time.
- Step 7: Display the priority-ordered schedule table and summary averages, then terminate.
Standard POSIX test parameters and terminal arguments.
#include <stdio.h>
int main() {
int n = 4;
int p[] = {1, 2, 3, 4};
int bt[] = {10, 1, 2, 1};
int pr[] = {3, 1, 4, 2}; // 1 = Highest Priority
int wt[4], tat[4];
float total_wt = 0, total_tat = 0;
for (int i = 0; i < n - 1; i++) {
for (int j = 0; j < n - i - 1; j++) {
if (pr[j] > pr[j + 1]) {
int t = pr[j]; pr[j] = pr[j + 1]; pr[j + 1] = t;
t = bt[j]; bt[j] = bt[j + 1]; bt[j + 1] = t;
t = p[j]; p[j] = p[j + 1]; p[j + 1] = t;
}
}
}
wt[0] = 0;
tat[0] = bt[0];
for (int i = 1; i < n; i++) {
wt[i] = wt[i - 1] + bt[i - 1];
tat[i] = wt[i] + bt[i];
}
printf("=== PRIORITY CPU SCHEDULING (C Program) ===\n");
printf("%-8s | %-8s | %-10s | %-12s | %-15s\n", "Process", "Priority", "Burst Time", "Waiting Time", "Turnaround Time");
printf("--------------------------------------------------------------\n");
for (int i = 0; i < n; i++) {
total_wt += wt[i]; total_tat += tat[i];
printf("P%-7d | %-8d | %-10d | %-12d | %-15d\n", p[i], pr[i], bt[i], wt[i], tat[i]);
}
printf("--------------------------------------------------------------\n");
printf("Average Waiting Time : %.2f ms\n", total_wt / n);
printf("Average Turnaround Time : %.2f ms\n", total_tat / n);
return 0;
}
Ans: The Convoy Effect occurs when numerous short I/O-bound processes are blocked waiting behind a single long CPU-bound process, causing high average waiting time.
Ans: Too large: RR degenerates into FCFS. Too small: CPU spends excessive time performing context switches instead of executing user code.
SOLUTION FOR PART (b) [MAX MARKS: 50]
50 MarksWrite a shell script to implement Priority Scheduling algorithm using arrays.
To demonstrate priority array sorting and schedule evaluation in Bash.
Shell: GNU Bash 5.2+ (Bourne-Again Shell) | Execution Command: chmod +x script.sh && ./script.sh
- Step 1: Start the program and read the number of processes, Burst Times, and Priority integer values.
- Step 2: Sort the processes in ascending order of Priority values (lower number represents higher priority).
- Step 3: Set Waiting Time for the highest priority process to 0: WT[0] = 0.
- Step 4: Calculate Waiting Time for subsequent processes: WT[i] = WT[i-1] + BT[i-1].
- Step 5: Calculate Turnaround Time for each process: TAT[i] = WT[i] + BT[i].
- Step 6: Compute Average Waiting Time and Average Turnaround Time.
- Step 7: Display the priority-ordered schedule table and summary averages, then terminate.
proc, priority, and burst based on priority integer values.
Standard POSIX test parameters and terminal arguments.
#!/bin/bash
# Priority Scheduling in Shell Script
p=(1 2 3 4)
bt=(10 1 2 1)
pr=(3 1 4 2)
n=${#p[@]}
for ((i=0; i<n-1; i++)); do
for ((j=0; j<n-i-1; j++)); do
if [ ${pr[j]} -gt ${pr[j+1]} ]; then
tmp=${pr[j]}; pr[j]=${pr[j+1]}; pr[j+1]=$tmp
tmp=${bt[j]}; bt[j]=${bt[j+1]}; bt[j+1]=$tmp
tmp=${p[j]}; p[j]=${p[j+1]}; p[j+1]=$tmp
fi
done
done
wt=(0)
tat=(${bt[0]})
total_wt=0
total_tat=${bt[0]}
for ((i=1; i<n; i++)); do
wt[$i]=$(( wt[i-1] + bt[i-1] ))
tat[$i]=$(( wt[i] + bt[i] ))
total_wt=$(( total_wt + wt[i] ))
total_tat=$(( total_tat + tat[i] ))
done
echo "=== PRIORITY SCHEDULING (Shell Script) ==="
printf "%-8s %-10s %-12s %-12s %-15s\n" "Process" "Priority" "Burst Time" "Waiting Time" "Turnaround Time"
echo "------------------------------------------------------------"
for ((i=0; i<n; i++)); do
printf "P%-7d %-10d %-12d %-12d %-15d\n" ${p[$i]} ${pr[$i]} ${bt[$i]} ${wt[$i]} ${tat[$i]}
done
echo "------------------------------------------------------------"
echo "Average Waiting Time : $(awk "BEGIN {printf \"%.2f\", $total_wt / $n}") ms"
echo "Average Turnaround Time : $(awk "BEGIN {printf \"%.2f\", $total_tat / $n}") ms"
Ans: The Convoy Effect occurs when numerous short I/O-bound processes are blocked waiting behind a single long CPU-bound process, causing high average waiting time.
Ans: Too large: RR degenerates into FCFS. Too small: CPU spends excessive time performing context switches instead of executing user code.
QUESTION NO: 15 (MODEL SET 1)
SOLUTION FOR PART (a) [MAX MARKS: 50]
50 MarksWrite a C program to implement Round Robin CPU scheduling algorithm for a given time quantum.
To implement preemptive Round Robin CPU scheduling using circular queue tracking with time quantum Q.
OS Environment: Linux / Kali Linux 6.x (POSIX Compliant) | Compiler: GCC 12.2+ (Flags: -Wall -pthread) | Standard: ISO C11 / POSIX IEEE 1003.1.
- Step 1: Start the program and input the number of processes, Burst Times, and Time Quantum (Q).
- Step 2: Create a copy of Burst Times into remaining burst time array rem_bt[]. Initialize current time t = 0.
- Step 3: Traverse processes in circular ready queue order.
- Step 4: If rem_bt[i] > Q, increment current time t by Q, and decrement rem_bt[i] by Q.
- Step 5: If rem_bt[i] <= Q and rem_bt[i] > 0, increment time t by rem_bt[i], compute Waiting Time WT[i] = t - BT[i], and set rem_bt[i] = 0.
- Step 6: Repeat until all processes have rem_bt[i] == 0.
- Step 7: Compute TAT[i] = BT[i] + WT[i], calculate averages, and print formatted schedule output, then terminate.
If remaining burst time <= Quantum, process runs to completion.
Processes = [P1, P2, P3] | Burst Times = [5, 4, 3] ms | Time Quantum Q = 2 ms
#include <stdio.h>
int main() {
int n = 3, quantum = 2;
int bt[] = {5, 4, 3};
int rem_bt[] = {5, 4, 3};
int wt[3] = {0}, tat[3] = {0};
int t = 0; // Current time
while (1) {
int done = 1;
for (int i = 0; i < n; i++) {
if (rem_bt[i] > 0) {
done = 0;
if (rem_bt[i] > quantum) {
t += quantum;
rem_bt[i] -= quantum;
} else {
t += rem_bt[i];
wt[i] = t - bt[i];
rem_bt[i] = 0;
}
}
}
if (done == 1) break;
}
float total_wt = 0, total_tat = 0;
for (int i = 0; i < n; i++) {
tat[i] = bt[i] + wt[i];
total_wt += wt[i];
total_tat += tat[i];
}
printf("=== ROUND ROBIN SCHEDULING (Quantum = %d) ===\n", quantum);
printf("%-8s | %-10s | %-12s | %-15s\n", "Process", "Burst Time", "Waiting Time", "Turnaround Time");
printf("---------------------------------------------------\n");
for (int i = 0; i < n; i++) {
printf("P%-7d | %-10d | %-12d | %-15d\n", i + 1, bt[i], wt[i], tat[i]);
}
printf("---------------------------------------------------\n");
printf("Average Waiting Time : %.2f ms\n", total_wt / n);
printf("Average Turnaround Time : %.2f ms\n", total_tat / n);
return 0;
}
Ans: The Convoy Effect occurs when numerous short I/O-bound processes are blocked waiting behind a single long CPU-bound process, causing high average waiting time.
Ans: Too large: RR degenerates into FCFS. Too small: CPU spends excessive time performing context switches instead of executing user code.
SOLUTION FOR PART (b) [MAX MARKS: 50]
50 MarksWrite a shell script to implement Round Robin CPU scheduling algorithm using a given time quantum.
To simulate round-robin preemptive time-slicing in Bash using remaining burst time arrays.
Shell: GNU Bash 5.2+ (Bourne-Again Shell) | Execution Command: chmod +x script.sh && ./script.sh
- Step 1: Start the program and input the number of processes, Burst Times, and Time Quantum (Q).
- Step 2: Create a copy of Burst Times into remaining burst time array rem_bt[]. Initialize current time t = 0.
- Step 3: Traverse processes in circular ready queue order.
- Step 4: If rem_bt[i] > Q, increment current time t by Q, and decrement rem_bt[i] by Q.
- Step 5: If rem_bt[i] <= Q and rem_bt[i] > 0, increment time t by rem_bt[i], compute Waiting Time WT[i] = t - BT[i], and set rem_bt[i] = 0.
- Step 6: Repeat until all processes have rem_bt[i] == 0.
- Step 7: Compute TAT[i] = BT[i] + WT[i], calculate averages, and print formatted schedule output, then terminate.
rem_bt array, deducting min(rem_bt[i], quantum) from active processes until all are zero.
Processes = [P1, P2, P3] | Burst Times = [5, 4, 3] ms | Time Quantum Q = 2 ms
#!/bin/bash
# Round Robin CPU Scheduling in Shell Script
quantum=2
bt=(5 4 3)
rem_bt=(5 4 3)
n=${#bt[@]}
wt=(0 0 0)
tat=(0 0 0)
t=0
while true; do
done_flag=1
for ((i=0; i<n; i++)); do
if [ ${rem_bt[$i]} -gt 0 ]; then
done_flag=0
if [ ${rem_bt[$i]} -gt $quantum ]; then
t=$((t + quantum))
rem_bt[$i]=$(( rem_bt[$i] - quantum ))
else
t=$((t + rem_bt[$i]))
wt[$i]=$((t - bt[$i]))
rem_bt[$i]=0
fi
fi
done
[ $done_flag -eq 1 ] && break
done
total_wt=0; total_tat=0
echo "=== ROUND ROBIN CPU SCHEDULING (Shell) ==="
printf "%-8s %-10s %-12s %-15s\n" "Process" "Burst Time" "Waiting Time" "Turnaround Time"
echo "---------------------------------------------------"
for ((i=0; i<n; i++)); do
tat[$i]=$(( bt[$i] + wt[$i] ))
total_wt=$((total_wt + wt[$i]))
total_tat=$((total_tat + tat[$i]))
printf "P%-7d %-10d %-12d %-15d\n" $((i+1)) ${bt[$i]} ${wt[$i]} ${tat[$i]}
done
echo "---------------------------------------------------"
echo "Average Waiting Time : $(awk "BEGIN {printf \"%.2f\", $total_wt / $n}") ms"
echo "Average Turnaround Time : $(awk "BEGIN {printf \"%.2f\", $total_tat / $n}") ms"
Ans: The Convoy Effect occurs when numerous short I/O-bound processes are blocked waiting behind a single long CPU-bound process, causing high average waiting time.
Ans: Too large: RR degenerates into FCFS. Too small: CPU spends excessive time performing context switches instead of executing user code.
QUESTION NO: 16 (MODEL SET 1)
SOLUTION FOR PART (a) [MAX MARKS: 50]
50 MarksTrace the FCFS algorithm for burst times 5, 3, 8, 6 and calculate the average waiting time.
To demonstrate rigorous step-by-step Gantt chart manual tracing and compute WT, TAT, and AWT.
OS Environment: Linux / Kali Linux 6.x (POSIX Compliant) | Compiler: GCC 12.2+ (Flags: -Wall -pthread) | Standard: ISO C11 / POSIX IEEE 1003.1.
- Step 1: Start the program and input the number of processes and their corresponding CPU Burst Times (BT).
- Step 2: Set Waiting Time of the first arriving process to zero: WT[0] = 0, and Turnaround Time TAT[0] = BT[0].
- Step 3: For each subsequent process i from 1 to n-1, calculate Waiting Time: WT[i] = WT[i-1] + BT[i-1].
- Step 4: Compute Turnaround Time for each process: TAT[i] = WT[i] + BT[i].
- Step 5: Accumulate total Waiting Time and total Turnaround Time across all processes.
- Step 6: Calculate Average Waiting Time = Total_WT / n and Average Turnaround Time = Total_TAT / n.
- Step 7: Print formatted table showing Process ID, Burst Time, Waiting Time, and Turnaround Time, then terminate.
β’ P1: BT = 5
β’ P2: BT = 3
β’ P3: BT = 8
β’ P4: BT = 6
(Assuming Arrival Time AT = 0 for all processes in order P1, P2, P3, P4)
| Process | Burst Time (BT) | Start Time | Completion Time (CT) | Turnaround Time (TAT = CT - AT) | Waiting Time (WT = TAT - BT) |
|---|---|---|---|---|---|
| P1 | 5 | 0 | 5 | 5 - 0 = 5 ms | 5 - 5 = 0 ms |
| P2 | 3 | 5 | 8 | 8 - 0 = 8 ms | 8 - 3 = 5 ms |
| P3 | 8 | 8 | 16 | 16 - 0 = 16 ms | 16 - 8 = 8 ms |
| P4 | 6 | 16 | 22 | 22 - 0 = 22 ms | 22 - 6 = 16 ms |
Processes = [P1, P2, P3, P4] | Burst Times = [5, 3, 8, 6] ms | Arrival Times = [0, 0, 0, 0] ms
Gantt Chart Representation:
+-------+-------+---------------+-----------+
| P1 | P2 | P3 | P4 |
+-------+-------+---------------+-----------+
0 5 8 16 22
Calculations:
Total Waiting Time (TWT) = 0 + 5 + 8 + 16 = 29 ms
Average Waiting Time (AWT) = TWT / 4 = 29 / 4 = 7.25 ms
Total Turnaround Time (TTAT) = 5 + 8 + 16 + 22 = 51 ms
Average Turnaround Time (ATAT) = TTAT / 4 = 51 / 4 = 12.75 ms
Ans: The Convoy Effect occurs when numerous short I/O-bound processes are blocked waiting behind a single long CPU-bound process, causing high average waiting time.
Ans: Too large: RR degenerates into FCFS. Too small: CPU spends excessive time performing context switches instead of executing user code.
SOLUTION FOR PART (b) [MAX MARKS: 50]
50 MarksModify the FCFS shell script to read burst times of five processes and compute average waiting time.
To create an interactive script accepting 5 user inputs and calculating exact FCFS metrics.
Shell: GNU Bash 5.2+ (Bourne-Again Shell) | Execution Command: chmod +x script.sh && ./script.sh
- Step 1: Start the program and input the number of processes and their corresponding CPU Burst Times (BT).
- Step 2: Set Waiting Time of the first arriving process to zero: WT[0] = 0, and Turnaround Time TAT[0] = BT[0].
- Step 3: For each subsequent process i from 1 to n-1, calculate Waiting Time: WT[i] = WT[i-1] + BT[i-1].
- Step 4: Compute Turnaround Time for each process: TAT[i] = WT[i] + BT[i].
- Step 5: Accumulate total Waiting Time and total Turnaround Time across all processes.
- Step 6: Calculate Average Waiting Time = Total_WT / n and Average Turnaround Time = Total_TAT / n.
- Step 7: Print formatted table showing Process ID, Burst Time, Waiting Time, and Turnaround Time, then terminate.
read, validates inputs, and computes running sums.
Standard POSIX test parameters and terminal arguments.
#!/bin/bash
# Shell Script: FCFS for 5 Processes
echo "=== FCFS SCHEDULING FOR 5 PROCESSES ==="
declare -a bt
declare -a wt
declare -a tat
for ((i=1; i<=5; i++)); do
read -p "Enter Burst Time for Process P$i: " b
bt[$((i-1))]=$b
done
wt[0]=0
tat[0]=${bt[0]}
tot_wt=0
tot_tat=${bt[0]}
for ((i=1; i<5; i++)); do
wt[$i]=$(( wt[i-1] + bt[i-1] ))
tat[$i]=$(( wt[i] + bt[i] ))
tot_wt=$(( tot_wt + wt[i] ))
tot_tat=$(( tot_tat + tat[i] ))
done
echo ""
printf "%-8s %-12s %-14s %-16s\n" "Process" "Burst Time" "Waiting Time" "Turnaround Time"
echo "--------------------------------------------------------"
for ((i=0; i<5; i++)); do
printf "P%-7d %-12d %-14d %-16d\n" $((i+1)) ${bt[$i]} ${wt[$i]} ${tat[$i]}
done
echo "--------------------------------------------------------"
echo "Total Waiting Time : $tot_wt ms"
echo "Average Waiting Time : $(awk "BEGIN {printf \"%.2f\", $tot_wt / 5}") ms"
echo "Average Turnaround : $(awk "BEGIN {printf \"%.2f\", $tot_tat / 5}") ms"
Ans: fork() returns -1 on error, 0 to the newly created child process, and the child's positive PID to the parent process.
Ans: An Orphan process is a running child whose parent has terminated (adopted by init/systemd). A Zombie process is a terminated process whose exit status has not yet been read by its parent via wait().
QUESTION NO: 17 (MODEL SET 1)
SOLUTION FOR PART (a) [MAX MARKS: 50]
50 MarksWrite a C program to implement Inter Process Communication (IPC) between parent and child processes using pipes.
To demonstrate unidirectional IPC by creating a half-duplex pipe using pipe() and fork().
OS Environment: Linux / Kali Linux 6.x (POSIX Compliant) | Compiler: GCC 12.2+ (Flags: -Wall -pthread) | Standard: ISO C11 / POSIX IEEE 1003.1.
- Step 1: Start the program and declare a two-element integer array for file descriptors: fd[2].
- Step 2: Call the system call pipe(fd) to create an anonymous unidirectional kernel buffer. Check for creation errors.
- Step 3: Invoke fork() system call to create a child process.
- Step 4: In the Writer process, close the unused reading file descriptor fd[0] using close(fd[0]).
- Step 5: Write the string message to fd[1] using write(fd[1], buffer, length) and close fd[1].
- Step 6: In the Reader process, close the unused writing file descriptor fd[1] using close(fd[1]).
- Step 7: Read the message from fd[0] using read(fd[0], buffer, size), display the content on console, and close fd[0].
- Step 8: Synchronize parent and child using wait() and terminate cleanly.
pipe(int fd[2]) creates an anonymous pipe channel:β’
fd[0] is open for reading.β’
fd[1] is open for writing.Child closes
fd[0] and writes; parent closes fd[1] and reads.
Standard POSIX test parameters and terminal arguments.
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>
int main() {
int fd[2];
pid_t pid;
char write_msg[] = "Hello from Child Process via Pipe!";
char read_msg[100];
if (pipe(fd) == -1) {
perror("Pipe creation failed");
return 1;
}
pid = fork();
if (pid < 0) {
perror("Fork failed");
return 1;
} else if (pid == 0) {
// Child Process -> Writer
close(fd[0]); // Close unused read end
printf("[CHILD] Writing message to pipe...\n");
write(fd[1], write_msg, strlen(write_msg) + 1);
close(fd[1]); // Close write end after sending
} else {
// Parent Process -> Reader
close(fd[1]); // Close unused write end
wait(NULL); // Wait for child to write
read(fd[0], read_msg, sizeof(read_msg));
printf("[PARENT] Successfully received message from pipe: '%s'\n", read_msg);
close(fd[0]); // Close read end
}
return 0;
}
Ans: fork() returns -1 on error, 0 to the newly created child process, and the child's positive PID to the parent process.
Ans: An Orphan process is a running child whose parent has terminated (adopted by init/systemd). A Zombie process is a terminated process whose exit status has not yet been read by its parent via wait().
SOLUTION FOR PART (b) [MAX MARKS: 50]
50 MarksWrite a shell script to demonstrate Inter Process Communication using UNIX pipes.
To demonstrate pipeline data streaming (|) and Named Pipes (FIFOs via mkfifo) in Bash.
Shell: GNU Bash 5.2+ (Bourne-Again Shell) | Execution Command: chmod +x script.sh && ./script.sh
- Step 1: Start the program and declare a two-element integer array for file descriptors: fd[2].
- Step 2: Call the system call pipe(fd) to create an anonymous unidirectional kernel buffer. Check for creation errors.
- Step 3: Invoke fork() system call to create a child process.
- Step 4: In the Writer process, close the unused reading file descriptor fd[0] using close(fd[0]).
- Step 5: Write the string message to fd[1] using write(fd[1], buffer, length) and close fd[1].
- Step 6: In the Reader process, close the unused writing file descriptor fd[1] using close(fd[1]).
- Step 7: Read the message from fd[0] using read(fd[0], buffer, size), display the content on console, and close fd[0].
- Step 8: Synchronize parent and child using wait() and terminate cleanly.
cmd1 | cmd2).Named pipes create a filesystem node (FIFO) allowing unrelated processes to communicate.
Standard POSIX test parameters and terminal arguments.
#!/bin/bash
# Shell Script: Demonstrating IPC via Anonymous and Named Pipes
echo "=== IPC DEMONSTRATION IN SHELL ==="
echo "1. Anonymous Pipeline IPC:"
echo "Process A" | tr 'a-z' 'A-Z' | sed 's/^/[Received by Process B]: /'
echo ""
echo "2. Named Pipe (FIFO) Demonstration:"
FIFO_PATH="/tmp/test_fifo_$"
mkfifo $FIFO_PATH
# Background writer process
(
echo "Message from background sender (PID: $BASHPID)" > $FIFO_PATH
) &
# Foreground reader process
read msg < $FIFO_PATH
echo "[Foreground Receiver]: Successfully read from FIFO -> '$msg'"
rm -f $FIFO_PATH
Ans: fork() returns -1 on error, 0 to the newly created child process, and the child's positive PID to the parent process.
Ans: An Orphan process is a running child whose parent has terminated (adopted by init/systemd). A Zombie process is a terminated process whose exit status has not yet been read by its parent via wait().
QUESTION NO: 18 (MODEL SET 1)
SOLUTION FOR PART (a) [MAX MARKS: 50]
50 MarksFor the reference string 7 0 1 2 0 3 0 4 2 3 0 3 2 with 3 frames, calculate the number of page faults using FIFO.
To manually trace FIFO page replacement for 13 page references using 3 memory frames.
OS Environment: Linux / Kali Linux 6.x (POSIX Compliant) | Compiler: GCC 12.2+ (Flags: -Wall -pthread) | Standard: ISO C11 / POSIX IEEE 1003.1.
- Step 1: Start the program and declare variables for reference string, memory frames, page faults, hits, and victim frame pointer.
- Step 2: Initialize all frame slots with -1 to indicate that physical memory frames are currently empty.
- Step 3: Read each page reference from the reference string one by one in sequential arrival order.
- Step 4: Check if the incoming page is already present in any allocated frame slot. If found, mark as PAGE HIT and proceed to next reference.
- Step 5: If the page is not found (PAGE FAULT), check if free frames exist. If available, allocate the empty frame directly.
- Step 6: If all frames are occupied, apply FIFO replacement: replace the frame at index 'victim_ptr' and advance the circular pointer: victim_ptr = (victim_ptr + 1) % total_frames.
- Step 7: Increment page fault counter and display the intermediate contents of all memory frames after replacement.
- Step 8: Compute final metrics: Hit Ratio = (Hits / Total) * 100 and Fault Ratio = (Faults / Total) * 100, then terminate.
| Step | Ref | F0 | F1 | F2 | Status | Victim Replaced |
|---|---|---|---|---|---|---|
| 1 | 7 | 7 | - | - | FAULT | Frame 0 allocated |
| 2 | 0 | 7 | 0 | - | FAULT | Frame 1 allocated |
| 3 | 1 | 7 | 0 | 1 | FAULT | Frame 2 allocated |
| 4 | 2 | 2 | 0 | 1 | FAULT | Replace 7 (F0) |
| 5 | 0 | 2 | 0 | 1 | HIT | 0 present |
| 6 | 3 | 2 | 3 | 1 | FAULT | Replace 0 (F1) |
| 7 | 0 | 2 | 3 | 0 | FAULT | Replace 1 (F2) |
| 8 | 4 | 4 | 3 | 0 | FAULT | Replace 2 (F0) |
| 9 | 2 | 4 | 2 | 0 | FAULT | Replace 3 (F1) |
| 10 | 3 | 4 | 2 | 3 | FAULT | Replace 0 (F2) |
| 11 | 0 | 0 | 2 | 3 | FAULT | Replace 4 (F0) |
| 12 | 3 | 0 | 2 | 3 | HIT | 3 present |
| 13 | 2 | 0 | 2 | 3 | HIT | 2 present |
Reference String = 7, 0, 1, 2, 0, 3, 0, 4, 2, 3, 0, 3, 2 | Frame Capacity = 3 Frames
Summary Statistics:
Total Memory References: 13
Total Page Faults (Misses): 10 Page Faults
Total Page Hits : 3 Page Hits
Hit Ratio : (3 / 13) * 100 = 23.08%
Fault Ratio : (10 / 13) * 100 = 76.92%
Ans: Belady's Anomaly is the counter-intuitive phenomenon where increasing the number of physical frames increases the number of page faults. FIFO is subject to it; stack algorithms like LRU and Optimal are strictly immune.
Ans: Because it requires future knowledge of all upcoming memory page references, which cannot be known in advance in a general-purpose OS.
SOLUTION FOR PART (b) [MAX MARKS: 50]
50 MarksExplain how a reference string and frame size are represented in a shell script for page replacement.
To demonstrate data structure mapping for page replacement simulations in Bash.
Shell: GNU Bash 5.2+ (Bourne-Again Shell) | Execution Command: chmod +x script.sh && ./script.sh
- Step 1: Start the program and initialize operating system parameters.
- Step 2: Allocate required memory buffers and validate input parameters.
- Step 3: Execute the core algorithmic logic ensuring adherence to OS synchronization and scheduling standards.
- Step 4: Format and print console output tables and execution verification metrics.
- Step 5: Release system resources and terminate program execution cleanly.
ref=(7 0 1 2 0 3 0 4 2 3 0 3 2).β’ Frames: Fixed-size array initialized to -1
frames=(-1 -1 -1).β’ Pointer: Circular index
victim_ptr=$(( (victim_ptr + 1) % frame_size )).
Standard POSIX test parameters and terminal arguments.
#!/bin/bash
# Shell Script Structure Representation
REF_STRING=(7 0 1 2 0 3 0 4 2 3 0 3 2)
FRAME_SIZE=3
FRAMES=()
# Initialize empty frames
for ((i=0; i<FRAME_SIZE; i++)); do
FRAMES[i]=-1
done
echo "Configuration:"
echo " Reference Array: ${REF_STRING[*]}"
echo " Frame Array : ${FRAMES[*]}"
echo " Frame Capacity : $FRAME_SIZE"
Ans: Belady's Anomaly is the counter-intuitive phenomenon where increasing the number of physical frames increases the number of page faults. FIFO is subject to it; stack algorithms like LRU and Optimal are strictly immune.
Ans: Because it requires future knowledge of all upcoming memory page references, which cannot be known in advance in a general-purpose OS.
QUESTION NO: 19 (MODEL SET 1)
SOLUTION FOR PART (a) [MAX MARKS: 50]
50 MarksGiven memory blocks 500, 200, 300, 600 and processes 357, 129, 191, allocate using Best Fit and show the result.
To manually trace the Best Fit allocation strategy where each process is allocated to the smallest block that is large enough.
OS Environment: Linux / Kali Linux 6.x (POSIX Compliant) | Compiler: GCC 12.2+ (Flags: -Wall -pthread) | Standard: ISO C11 / POSIX IEEE 1003.1.
- Step 1: Start the program and input available memory block sizes and incoming process memory requests.
- Step 2: Initialize the allocation array with -1 to indicate all processes are initially unallocated.
- Step 3: For each process request, search through all available memory blocks.
- Step 4: Among all blocks whose size is >= process size, find the block index that has the minimum remaining space: min(blockSize - processSize).
- Step 5: If an optimal block is found, allocate it to the process, record the block index, and subtract process size from block capacity.
- Step 6: If no block can satisfy the process, flag it as 'Not Allocated / Must Wait'.
- Step 7: Display the final allocation table showing Process ID, Process Size, Allocated Block, and Internal Fragmentation, then terminate.
min(Block_Size - Process_Size) >= 0.Minimizes wasted internal fragmentation per block.
| Process | Process Size | Eligible Free Blocks | Selected Best Fit Block | Remaining Block Size (Internal Fragmentation) |
|---|---|---|---|---|
| P1 | 357 | Block 1 (500), Block 4 (600) | Block 1 (500) [Waste = 500 - 357 = 143] | 143 KB |
| P2 | 129 | Block 2 (200), Block 3 (300), Block 4 (600) | Block 2 (200) [Waste = 200 - 129 = 71] | 71 KB |
| P3 | 191 | Block 3 (300), Block 4 (600) | Block 3 (300) [Waste = 300 - 191 = 109] | 109 KB |
Memory Blocks = [500, 200, 300, 600] KB | Process Requests = [357, 129, 191] KB
Final Allocation Status:
Process P1 (357 KB) -> Allocated to Block 1 (500 KB) | Remaining = 143 KB
Process P2 (129 KB) -> Allocated to Block 2 (200 KB) | Remaining = 71 KB
Process P3 (191 KB) -> Allocated to Block 3 (300 KB) | Remaining = 109 KB
Unallocated Free Block:
Block 4 (600 KB) -> Completely Unused / Free!
Total Memory Allocated = 357 + 129 + 191 = 677 KB
Total Internal Fragmentation = 143 + 71 + 109 = 323 KB
Ans: fork() returns -1 on error, 0 to the newly created child process, and the child's positive PID to the parent process.
Ans: An Orphan process is a running child whose parent has terminated (adopted by init/systemd). A Zombie process is a terminated process whose exit status has not yet been read by its parent via wait().
SOLUTION FOR PART (b) [MAX MARKS: 50]
50 MarksWrite a shell script to display memory blocks before and after a simulated allocation.
To demonstrate memory state logging and dynamic updates in a Bash script.
Shell: GNU Bash 5.2+ (Bourne-Again Shell) | Execution Command: chmod +x script.sh && ./script.sh
- Step 1: Start the program and initialize operating system parameters.
- Step 2: Allocate required memory buffers and validate input parameters.
- Step 3: Execute the core algorithmic logic ensuring adherence to OS synchronization and scheduling standards.
- Step 4: Format and print console output tables and execution verification metrics.
- Step 5: Release system resources and terminate program execution cleanly.
Standard POSIX test parameters and terminal arguments.
#!/bin/bash
# Shell Script: Best Fit Memory Allocation
blocks=(500 200 300 600)
processes=(357 129 191)
echo "=== MEMORY ALLOCATION SIMULATION (BEST FIT) ==="
echo "Initial Memory Blocks: [${blocks[*]}]"
echo "Incoming Processes : [${processes[*]}]"
echo ""
for ((i=0; i<${#processes[@]}; i++)); do
p=${processes[$i]}
best_idx=-1
for ((j=0; j<${#blocks[@]}; j++)); do
if [ ${blocks[$j]} -ge $p ]; then
if [ $best_idx -eq -1 ] || [ ${blocks[$j]} -lt ${blocks[$best_idx]} ]; then
best_idx=$j
fi
fi
done
if [ $best_idx -ne -1 ]; then
echo "Allocating P$((i+1)) ($p KB) -> Block $((best_idx+1)) (Size: ${blocks[$best_idx]} KB)"
blocks[$best_idx]=$(( blocks[$best_idx] - p ))
else
echo "P$((i+1)) ($p KB) -> NOT ALLOCATED (Insufficient space)"
fi
done
echo ""
echo "Remaining Memory Blocks After Allocation: [${blocks[*]}]"
Ans: User Mode executes unprivileged user applications with limited hardware access. Kernel Mode executes privileged OS instructions with unrestricted access to memory and CPU registers.
QUESTION NO: 20 (MODEL SET 1)
SOLUTION FOR PART (a) [MAX MARKS: 50]
50 MarksWrite a C program to compute the page number and offset for a given logical address and page size.
To implement mathematical address translation for paging architecture in C.
OS Environment: Linux / Kali Linux 6.x (POSIX Compliant) | Compiler: GCC 12.2+ (Flags: -Wall -pthread) | Standard: ISO C11 / POSIX IEEE 1003.1.
- Step 1: Start the program and configure Page Size (PS) and total number of pages in the process Page Table.
- Step 2: Read the Logical Address (LA) requested by the CPU.
- Step 3: Compute Page Number: page_no = LA / PS.
- Step 4: Compute Byte Offset: offset = LA % PS.
- Step 5: Check MMU bounds: If page_no >= Total_Pages, trigger internal Hardware Trap (Addressing Exception / Segmentation Fault SIGSEGV).
- Step 6: If page_no is valid, retrieve physical Frame Number from page table: frame_no = Page_Table[page_no].
- Step 7: Calculate Physical Address = (frame_no * PS) + offset and display translated physical address, then terminate.
LA and Page Size PS:β’
Page Number (p) = LA / PSβ’
Offset (d) = LA % PSUsing bitwise arithmetic when
PS = 2^k:β’
p = LA >> kβ’
d = LA & ((1 << k) - 1)
Logical Address = 2500 bytes (or 7250 bytes) | Page Size = 1024 bytes (1 KB)
#include <stdio.h>
void calculatePaging(unsigned int logical_addr, unsigned int page_size) {
unsigned int page_number = logical_addr / page_size;
unsigned int offset = logical_addr % page_size;
printf("Logical Address : %u bytes\n", logical_addr);
printf("Page Size : %u bytes\n", page_size);
printf("-----------------------------------------\n");
printf("Derived Page No : %u\n", page_number);
printf("Derived Offset : %u bytes\n", offset);
printf("Verification : (%u * %u) + %u = %u\n\n",
page_number, page_size, offset, (page_number * page_size) + offset);
}
int main() {
printf("=== LOGICAL ADDRESS TRANSLATOR (C) ===\n");
calculatePaging(7250, 1024); // 1 KB page size
calculatePaging(18500, 4096); // 4 KB page size
return 0;
}
Ans: User Mode executes unprivileged user applications with limited hardware access. Kernel Mode executes privileged OS instructions with unrestricted access to memory and CPU registers.
SOLUTION FOR PART (b) [MAX MARKS: 50]
50 MarksExplain how arrays are used in shell scripting to represent a page table.
To demonstrate representation and address mapping of page tables in Bash.
Shell: GNU Bash 5.2+ (Bourne-Again Shell) | Execution Command: chmod +x script.sh && ./script.sh
- Step 1: Start the program and configure Page Size (PS) and total number of pages in the process Page Table.
- Step 2: Read the Logical Address (LA) requested by the CPU.
- Step 3: Compute Page Number: page_no = LA / PS.
- Step 4: Compute Byte Offset: offset = LA % PS.
- Step 5: Check MMU bounds: If page_no >= Total_Pages, trigger internal Hardware Trap (Addressing Exception / Segmentation Fault SIGSEGV).
- Step 6: If page_no is valid, retrieve physical Frame Number from page table: frame_no = Page_Table[page_no].
- Step 7: Calculate Physical Address = (frame_no * PS) + offset and display translated physical address, then terminate.
β’ Array index = Page Number
β’ Array element = Physical Frame Number
Address translation in shell:
physical_address = (page_table[page] * page_size) + offset.
Standard POSIX test parameters and terminal arguments.
#!/bin/bash
# Shell Script: Page Table Array Translation
PAGE_SIZE=1024
# Page Table: Page 0->Frame 5, Page 1->Frame 2, Page 2->Frame 8, Page 3->Frame 1
page_table=(5 2 8 1)
echo "=== SHELL SCRIPT PAGE TABLE TRANSLATION ==="
echo "Page Size : $PAGE_SIZE bytes"
echo "Page Table : Page 0->Frame 5 | Page 1->Frame 2 | Page 2->Frame 8 | Page 3->Frame 1"
echo ""
read -p "Enter Logical Address: " la
p=$(( la / PAGE_SIZE ))
d=$(( la % PAGE_SIZE ))
if [ $p -ge ${#page_table[@]} ]; then
echo "ERROR: Page $p out of range! Segmentation Fault."
exit 1
fi
frame=${page_table[$p]}
pa=$(( (frame * PAGE_SIZE) + d ))
echo "Page Number : $p"
echo "Offset : $d bytes"
echo "Mapped Frame : $frame"
echo "Physical Address: $pa bytes"
Ans: User Mode executes unprivileged user applications with limited hardware access. Kernel Mode executes privileged OS instructions with unrestricted access to memory and CPU registers.
Model Practical Examination Paper 2 (Questions 1 β 20)
Advanced problem sets covering IPC, Semaphores, Banker's Algorithm, Deadlocks, Pthreads, and Memory Allocation.
QUESTION NO: 1 (MODEL SET 2)
SOLUTION FOR PART (a) [MAX MARKS: 50]
50 MarksExplain the pipe() system call with its syntax and write a C program to demonstrate one-way communication.
To explain anonymous pipe mechanics and implement unidirectional parent-child IPC in C.
OS Environment: Linux / Kali Linux 6.x (POSIX Compliant) | Compiler: GCC 12.2+ (Flags: -Wall -pthread) | Standard: ISO C11 / POSIX IEEE 1003.1.
- Step 1: Start the program and declare a two-element integer array for file descriptors: fd[2].
- Step 2: Call the system call pipe(fd) to create an anonymous unidirectional kernel buffer. Check for creation errors.
- Step 3: Invoke fork() system call to create a child process.
- Step 4: In the Writer process, close the unused reading file descriptor fd[0] using close(fd[0]).
- Step 5: Write the string message to fd[1] using write(fd[1], buffer, length) and close fd[1].
- Step 6: In the Reader process, close the unused writing file descriptor fd[1] using close(fd[1]).
- Step 7: Read the message from fd[0] using read(fd[0], buffer, size), display the content on console, and close fd[0].
- Step 8: Synchronize parent and child using wait() and terminate cleanly.
int pipe(int pipefd[2]); creates a unidirectional data channel in kernel memory:β’
pipefd[0] refers to the read end of the pipe.β’
pipefd[1] refers to the write end of the pipe.β’ Bytes written to
pipefd[1] are buffered until read from pipefd[0] in FIFO order.β’ Return Value:
0 on success, -1 on failure (with errno set).
Standard POSIX test parameters and terminal arguments.
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
int main() {
int pfd[2];
pid_t pid;
char write_buf[] = "System Call Communication: Kernel Pipe Active";
char read_buf[100];
if (pipe(pfd) == -1) {
perror("pipe creation failed");
return 1;
}
pid = fork();
if (pid < 0) {
perror("fork failed");
return 1;
} else if (pid == 0) {
// Child acts as reader
close(pfd[1]); // Close unused write descriptor
read(pfd[0], read_buf, sizeof(read_buf));
printf("[CHILD READER] Received message: "%s"\n", read_buf);
close(pfd[0]);
} else {
// Parent acts as writer
close(pfd[0]); // Close unused read descriptor
printf("[PARENT WRITER] Transmitting buffer through pipefd[1]...\n");
write(pfd[1], write_buf, strlen(write_buf) + 1);
close(pfd[1]);
wait(NULL); // Synchronize with child
}
return 0;
}
Ans: Anonymous pipes are half-duplex, kernel-buffered channels shared only between related processes (parent-child). Named pipes exist as filesystem nodes and allow unrelated processes to communicate.
SOLUTION FOR PART (b) [MAX MARKS: 50]
50 MarksWrite a shell script to send a message from one process to another using the pipe operator and read command.
To demonstrate pipeline data streaming (|) combined with standard read command consumption in Bash.
Shell: GNU Bash 5.2+ (Bourne-Again Shell) | Execution Command: chmod +x script.sh && ./script.sh
- Step 1: Start the program and declare a two-element integer array for file descriptors: fd[2].
- Step 2: Call the system call pipe(fd) to create an anonymous unidirectional kernel buffer. Check for creation errors.
- Step 3: Invoke fork() system call to create a child process.
- Step 4: In the Writer process, close the unused reading file descriptor fd[0] using close(fd[0]).
- Step 5: Write the string message to fd[1] using write(fd[1], buffer, length) and close fd[1].
- Step 6: In the Reader process, close the unused writing file descriptor fd[1] using close(fd[1]).
- Step 7: Read the message from fd[0] using read(fd[0], buffer, size), display the content on console, and close fd[0].
- Step 8: Synchronize parent and child using wait() and terminate cleanly.
| connects standard output of the producer process to standard input of the consumer process in separate concurrent subshells.
Standard POSIX test parameters and terminal arguments.
#!/bin/bash
# Shell Script: IPC using pipe operator and read command
echo "Initiating Pipeline Process Communication..."
# Process 1 streams text; Process 2 reads it into variable
echo "Confidential Data Stream from Subshell 1" | {
read received_msg
echo "[Consumer Subshell]: Successfully intercepted message -> '$received_msg'"
echo "[Consumer Subshell]: Processing token count: $(echo $received_msg | wc -w) words"
}
echo "Pipeline execution completed."
Ans: fork() returns -1 on error, 0 to the newly created child process, and the child's positive PID to the parent process.
Ans: An Orphan process is a running child whose parent has terminated (adopted by init/systemd). A Zombie process is a terminated process whose exit status has not yet been read by its parent via wait().
QUESTION NO: 2 (MODEL SET 2)
SOLUTION FOR PART (a) [MAX MARKS: 50]
50 MarksWrite a C program to send a message from a child process to a parent process using a pipe and display it.
To demonstrate reverse unidirectional IPC where child process transmits telemetry data to parent process.
OS Environment: Linux / Kali Linux 6.x (POSIX Compliant) | Compiler: GCC 12.2+ (Flags: -Wall -pthread) | Standard: ISO C11 / POSIX IEEE 1003.1.
- Step 1: Start the program and declare a two-element integer array for file descriptors: fd[2].
- Step 2: Call the system call pipe(fd) to create an anonymous unidirectional kernel buffer. Check for creation errors.
- Step 3: Invoke fork() system call to create a child process.
- Step 4: In the Writer process, close the unused reading file descriptor fd[0] using close(fd[0]).
- Step 5: Write the string message to fd[1] using write(fd[1], buffer, length) and close fd[1].
- Step 6: In the Reader process, close the unused writing file descriptor fd[1] using close(fd[1]).
- Step 7: Read the message from fd[0] using read(fd[0], buffer, size), display the content on console, and close fd[0].
- Step 8: Synchronize parent and child using wait() and terminate cleanly.
pipe(fd).Child writes to
fd[1] and closes it. Parent waits and reads from fd[0].
Standard POSIX test parameters and terminal arguments.
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <sys/wait.h>
int main() {
int fd[2];
pid_t pid;
char child_msg[] = "STATUS_OK: Child task executed successfully.";
char buffer[128];
if (pipe(fd) < 0) { perror("Pipe failed"); exit(1); }
pid = fork();
if (pid < 0) { perror("Fork failed"); exit(1); }
if (pid == 0) {
// Child Process -> Producer
close(fd[0]); // Close read end
printf("[CHILD (PID: %d)] Sending telemetry to parent...\n", getpid());
write(fd[1], child_msg, strlen(child_msg) + 1);
close(fd[1]);
exit(0);
} else {
// Parent Process -> Consumer
close(fd[1]); // Close write end
wait(NULL); // Wait for child
read(fd[0], buffer, sizeof(buffer));
printf("[PARENT (PID: %d)] Received packet from child: '%s'\n", getpid(), buffer);
close(fd[0]);
}
return 0;
}
Ans: fork() returns -1 on error, 0 to the newly created child process, and the child's positive PID to the parent process.
Ans: An Orphan process is a running child whose parent has terminated (adopted by init/systemd). A Zombie process is a terminated process whose exit status has not yet been read by its parent via wait().
SOLUTION FOR PART (b) [MAX MARKS: 50]
50 MarksExplain how shell scripting simulates IPC using pipes, with a suitable example script.
To explain how Bash simulates IPC channels using Anonymous Pipes (|) and Named Pipes (mkfifo).
Shell: GNU Bash 5.2+ (Bourne-Again Shell) | Execution Command: chmod +x script.sh && ./script.sh
- Step 1: Start the program and declare a two-element integer array for file descriptors: fd[2].
- Step 2: Call the system call pipe(fd) to create an anonymous unidirectional kernel buffer. Check for creation errors.
- Step 3: Invoke fork() system call to create a child process.
- Step 4: In the Writer process, close the unused reading file descriptor fd[0] using close(fd[0]).
- Step 5: Write the string message to fd[1] using write(fd[1], buffer, length) and close fd[1].
- Step 6: In the Reader process, close the unused writing file descriptor fd[1] using close(fd[1]).
- Step 7: Read the message from fd[0] using read(fd[0], buffer, size), display the content on console, and close fd[0].
- Step 8: Synchronize parent and child using wait() and terminate cleanly.
Writing blocks until a reader attaches, maintaining synchronization.
Standard POSIX test parameters and terminal arguments.
#!/bin/bash
# Shell Script: Named Pipe (FIFO) IPC Simulation
FIFO_PIPE="/tmp/os_lab_pipe_$"
mkfifo $FIFO_PIPE
echo "Created Named Pipe: $FIFO_PIPE"
# Producer Process (Child)
(
echo "Child Process (PID: $BASHPID) computing sensor telemetry..."
sleep 1
echo "SENSOR_DATA: TEMP=28.4C, PRESSURE=1013hPa" > $FIFO_PIPE
) &
# Consumer Process (Parent)
echo "Parent Shell ($) reading from FIFO..."
read received_packet < $FIFO_PIPE
echo "Parent successfully captured: $received_packet"
rm -f $FIFO_PIPE
Ans: Anonymous pipes are half-duplex, kernel-buffered channels shared only between related processes (parent-child). Named pipes exist as filesystem nodes and allow unrelated processes to communicate.
QUESTION NO: 3 (MODEL SET 2)
SOLUTION FOR PART (a) [MAX MARKS: 50]
50 MarksWrite a C program to implement mutual exclusion using semaphores for two processes.
To enforce Mutual Exclusion (Mutex) across two concurrent execution threads/processes using POSIX semaphores.
OS Environment: Linux / Kali Linux 6.x (POSIX Compliant) | Compiler: GCC 12.2+ (Flags: -Wall -pthread) | Standard: ISO C11 / POSIX IEEE 1003.1.
- Step 1: Start the program, include
, and declare a semaphore variable of type sem_t. - Step 2: Initialize the semaphore using sem_init(&sem, 0, 1) to configure a binary mutual exclusion lock.
- Step 3: Spawn concurrent execution threads or processes that require access to the shared critical section.
- Step 4: In Entry Section, invoke sem_wait(&sem) (P-operation). The thread decrements the semaphore and acquires lock.
- Step 5: Execute the Critical Section: update the shared resource exclusively without race conditions.
- Step 6: In Exit Section, invoke sem_post(&sem) (V-operation). The thread increments the semaphore and awakens any waiting threads.
- Step 7: Join all threads with pthread_join() and destroy the semaphore using sem_destroy(&sem), then terminate.
1 acts as a mutex:β’
sem_wait(&mutex): Decrements semaphore. If value <= 0, process blocks. (Entry Section)β’ Critical Section: Only one process executes at any given time.
β’
sem_post(&mutex): Increments semaphore, waking up blocked waiting process. (Exit Section)
Standard POSIX test parameters and terminal arguments.
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#include <unistd.h>
sem_t mutex;
int shared_counter = 0;
void* process_worker(void* arg) {
long id = (long)arg;
// Entry Section
sem_wait(&mutex);
// Critical Section
printf("[PROCESS/THREAD %ld] Entered Critical Section. Counter = %d\n", id, shared_counter);
int temp = shared_counter;
sleep(1); // Simulate time spent in Critical Section
shared_counter = temp + 1;
printf("[PROCESS/THREAD %ld] Exiting Critical Section. New Counter = %d\n", id, shared_counter);
// Exit Section
sem_post(&mutex);
return NULL;
}
int main() {
pthread_t t1, t2;
sem_init(&mutex, 0, 1); // Binary semaphore initialized to 1
pthread_create(&t1, NULL, process_worker, (void*)1);
pthread_create(&t2, NULL, process_worker, (void*)2);
pthread_join(t1, NULL);
pthread_join(t2, NULL);
sem_destroy(&mutex);
printf("Final Shared Counter: %d (Expected: 2)\n", shared_counter);
return 0;
}
Ans: fork() returns -1 on error, 0 to the newly created child process, and the child's positive PID to the parent process.
Ans: An Orphan process is a running child whose parent has terminated (adopted by init/systemd). A Zombie process is a terminated process whose exit status has not yet been read by its parent via wait().
SOLUTION FOR PART (b) [MAX MARKS: 50]
50 MarksWrite a shell script to implement mutual exclusion using a lock file mechanism.
To implement mutual exclusion in Bash using atomic filesystem lock creation.
Shell: GNU Bash 5.2+ (Bourne-Again Shell) | Execution Command: chmod +x script.sh && ./script.sh
- Step 1: Start the program and initialize operating system parameters.
- Step 2: Allocate required memory buffers and validate input parameters.
- Step 3: Execute the core algorithmic logic ensuring adherence to OS synchronization and scheduling standards.
- Step 4: Format and print console output tables and execution verification metrics.
- Step 5: Release system resources and terminate program execution cleanly.
mkdir or noclobber file creation is atomic at the kernel VFS layer.If lock file exists, second process waits; once first process removes lock, second enters.
Standard POSIX test parameters and terminal arguments.
#!/bin/bash
# Mutual Exclusion in Shell Script using Lock File
LOCK_FILE="/tmp/crit_sec.lock"
acquire_lock() {
local pid=$
while ! ( set -o noclobber; echo "$pid" > "$LOCK_FILE" ) 2> /dev/null; do
echo "Process $pid: Lock busy! Waiting..."
sleep 1
done
echo "Process $pid: Lock ACQUIRED."
}
release_lock() {
local pid=$
rm -f "$LOCK_FILE"
echo "Process $pid: Lock RELEASED."
}
# Simulate Critical Section
echo "--- Testing Lock File Mutual Exclusion ---"
acquire_lock
echo ">>> Process $ executing INSIDE CRITICAL SECTION <<<"
sleep 2
release_lock
Ans: User Mode executes unprivileged user applications with limited hardware access. Kernel Mode executes privileged OS instructions with unrestricted access to memory and CPU registers.
QUESTION NO: 4 (MODEL SET 2)
SOLUTION FOR PART (a) [MAX MARKS: 50]
50 MarksExplain sem_init(), sem_wait(), and sem_post() functions with their syntax.
To analyze POSIX counting and binary semaphore system primitives with syntax, parameters, and operation semantics.
OS Environment: Linux / Kali Linux 6.x (POSIX Compliant) | Compiler: GCC 12.2+ (Flags: -Wall -pthread) | Standard: ISO C11 / POSIX IEEE 1003.1.
- Step 1: Start the program and initialize operating system parameters.
- Step 2: Allocate required memory buffers and validate input parameters.
- Step 3: Execute the core algorithmic logic ensuring adherence to OS synchronization and scheduling standards.
- Step 4: Format and print console output tables and execution verification metrics.
- Step 5: Release system resources and terminate program execution cleanly.
#include <semaphore.h>1. sem_init:
int sem_init(sem_t *sem, int pshared, unsigned int value);β’
sem: Pointer to semaphore object.β’
pshared: 0 for thread sharing within process; non-zero for sharing across processes (in shared memory).β’
value: Initial semaphore integer value.2. sem_wait:
int sem_wait(sem_t *sem);β’ Atomic decrement (P operation). If value > 0, decrements and proceeds; if value == 0, calling thread blocks.
3. sem_post:
int sem_post(sem_t *sem);β’ Atomic increment (V operation). Increments value; if threads blocked, wakes one up.
Standard POSIX test parameters and terminal arguments.
/* Usage Template */
sem_t s;
sem_init(&s, 0, 1); // Mutex
sem_wait(&s);
// Critical Section code here
sem_post(&s);
sem_destroy(&s);
Ans: User Mode executes unprivileged user applications with limited hardware access. Kernel Mode executes privileged OS instructions with unrestricted access to memory and CPU registers.
SOLUTION FOR PART (b) [MAX MARKS: 50]
50 MarksExplain how a lock file simulates semaphore behaviour in shell scripting, with a script example.
To demonstrate how file existence flags in the filesystem emulate binary semaphore state transitions (0 and 1).
Shell: GNU Bash 5.2+ (Bourne-Again Shell) | Execution Command: chmod +x script.sh && ./script.sh
- Step 1: Start the program, include
, and declare a semaphore variable of type sem_t. - Step 2: Initialize the semaphore using sem_init(&sem, 0, 1) to configure a binary mutual exclusion lock.
- Step 3: Spawn concurrent execution threads or processes that require access to the shared critical section.
- Step 4: In Entry Section, invoke sem_wait(&sem) (P-operation). The thread decrements the semaphore and acquires lock.
- Step 5: Execute the Critical Section: update the shared resource exclusively without race conditions.
- Step 6: In Exit Section, invoke sem_post(&sem) (V-operation). The thread increments the semaphore and awakens any waiting threads.
- Step 7: Join all threads with pthread_join() and destroy the semaphore using sem_destroy(&sem), then terminate.
β’ Semaphore Value = 1: Lock file absent.
β’
sem_wait(): Spin-wait loop checks file existence; when absent, atomically creates file.β’
sem_post(): Deletes lock file, allowing other waiting scripts to enter.
Standard POSIX test parameters and terminal arguments.
#!/bin/bash
# Simulating Semaphore using Lock File in Shell
LOCK_DIR="/tmp/sem_lock_dir"
sem_wait_sim() {
while ! mkdir "$LOCK_DIR" 2>/dev/null; do
echo "Worker $: Semaphore is 0 (Blocked). Waiting..."
sleep 1
done
echo "Worker $: sem_wait() passed. Value = 0 (Lock Held)"
}
sem_post_sim() {
rmdir "$LOCK_DIR"
echo "Worker $: sem_post() executed. Value = 1 (Lock Released)"
}
echo "Starting Semaphore Simulation..."
sem_wait_sim
echo ">>> Worker $ in Critical Section <<<"
sleep 2
sem_post_sim
Ans: 1) Mutual Exclusion (at most one process in CS), 2) Progress (selection of next process cannot be stalled indefinitely), and 3) Bounded Waiting (finite limit on entries before a waiting process is admitted).
QUESTION NO: 5 (MODEL SET 2)
SOLUTION FOR PART (a) [MAX MARKS: 50]
50 MarksWrite a C program to demonstrate the use of a semaphore to control access to a critical section.
To verify that POSIX semaphores ensure only one execution context modifies a shared resource at any instant.
OS Environment: Linux / Kali Linux 6.x (POSIX Compliant) | Compiler: GCC 12.2+ (Flags: -Wall -pthread) | Standard: ISO C11 / POSIX IEEE 1003.1.
- Step 1: Start the program, include
, and declare a semaphore variable of type sem_t. - Step 2: Initialize the semaphore using sem_init(&sem, 0, 1) to configure a binary mutual exclusion lock.
- Step 3: Spawn concurrent execution threads or processes that require access to the shared critical section.
- Step 4: In Entry Section, invoke sem_wait(&sem) (P-operation). The thread decrements the semaphore and acquires lock.
- Step 5: Execute the Critical Section: update the shared resource exclusively without race conditions.
- Step 6: In Exit Section, invoke sem_post(&sem) (V-operation). The thread increments the semaphore and awakens any waiting threads.
- Step 7: Join all threads with pthread_join() and destroy the semaphore using sem_destroy(&sem), then terminate.
1. Mutual Exclusion: At most one process in Critical Section.
2. Progress: Selection cannot be postponed indefinitely.
3. Bounded Waiting: Bound exists on number of times other processes enter.
Standard POSIX test parameters and terminal arguments.
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#include <unistd.h>
sem_t cs_sem;
void* worker(void* arg) {
int id = *((int*)arg);
printf("Thread %d: Requesting entry into Critical Section...\n", id);
sem_wait(&cs_sem); // P operation
printf(">>> Thread %d: GRANTED entry. Inside Critical Section.\n", id);
sleep(2); // In CS
printf("<<< Thread %d: LEAVING Critical Section.\n", id);
sem_post(&cs_sem); // V operation
return NULL;
}
int main() {
pthread_t t1, t2;
int id1 = 1, id2 = 2;
sem_init(&cs_sem, 0, 1);
pthread_create(&t1, NULL, worker, &id1);
pthread_create(&t2, NULL, worker, &id2);
pthread_join(t1, NULL);
pthread_join(t2, NULL);
sem_destroy(&cs_sem);
return 0;
}
Ans: 1) Mutual Exclusion (at most one process in CS), 2) Progress (selection of next process cannot be stalled indefinitely), and 3) Bounded Waiting (finite limit on entries before a waiting process is admitted).
SOLUTION FOR PART (b) [MAX MARKS: 50]
50 MarksWrite a shell script to ensure only one process accesses the critical section at a time using a lock file.
To launch concurrent background processes and demonstrate sequential access to a critical resource via lock file.
Shell: GNU Bash 5.2+ (Bourne-Again Shell) | Execution Command: chmod +x script.sh && ./script.sh
- Step 1: Start the program and initialize operating system parameters.
- Step 2: Allocate required memory buffers and validate input parameters.
- Step 3: Execute the core algorithmic logic ensuring adherence to OS synchronization and scheduling standards.
- Step 4: Format and print console output tables and execution verification metrics.
- Step 5: Release system resources and terminate program execution cleanly.
Standard POSIX test parameters and terminal arguments.
#!/bin/bash
# Shell Script: Critical Section protection for 2 concurrent jobs
LOCK_FILE="/tmp/cs_demo.lock"
SHARED_LOG="/tmp/cs_shared.txt"
rm -f $LOCK_FILE $SHARED_LOG
run_worker() {
local id=$1
echo "[Worker $id]: Competing for Critical Section..."
while ! ( set -o noclobber; echo "$id" > "$LOCK_FILE" ) 2>/dev/null; do
sleep 0.5
done
echo ">>> [Worker $id]: Entered Critical Section at $(date +%T)" >> $SHARED_LOG
sleep 2
echo "<<< [Worker $id]: Leaving Critical Section at $(date +%T)" >> $SHARED_LOG
rm -f "$LOCK_FILE"
}
run_worker "A" &
run_worker "B" &
wait
cat $SHARED_LOG
rm -f $SHARED_LOG
Ans: fork() returns -1 on error, 0 to the newly created child process, and the child's positive PID to the parent process.
Ans: An Orphan process is a running child whose parent has terminated (adopted by init/systemd). A Zombie process is a terminated process whose exit status has not yet been read by its parent via wait().
QUESTION NO: 6 (MODEL SET 2)
SOLUTION FOR PART (a) [MAX MARKS: 50]
50 MarksExplain the concept of race condition and how semaphores prevent it, with a C program example.
To demonstrate how concurrent read-modify-write operations produce inconsistent state and how semaphores serialize execution.
OS Environment: Linux / Kali Linux 6.x (POSIX Compliant) | Compiler: GCC 12.2+ (Flags: -Wall -pthread) | Standard: ISO C11 / POSIX IEEE 1003.1.
- Step 1: Start the program, include
, and declare a semaphore variable of type sem_t. - Step 2: Initialize the semaphore using sem_init(&sem, 0, 1) to configure a binary mutual exclusion lock.
- Step 3: Spawn concurrent execution threads or processes that require access to the shared critical section.
- Step 4: In Entry Section, invoke sem_wait(&sem) (P-operation). The thread decrements the semaphore and acquires lock.
- Step 5: Execute the Critical Section: update the shared resource exclusively without race conditions.
- Step 6: In Exit Section, invoke sem_post(&sem) (V-operation). The thread increments the semaphore and awakens any waiting threads.
- Step 7: Join all threads with pthread_join() and destroy the semaphore using sem_destroy(&sem), then terminate.
Example:
counter++ compiles to three assembly instructions:1.
MOV EAX, [counter]2.
ADD EAX, 13.
MOV [counter], EAXIf preempted between steps 1 and 3, increments are lost. Semaphores make this sequence atomic.
Standard POSIX test parameters and terminal arguments.
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#define ITERATIONS 100000
int counter = 0;
sem_t lock;
void* thread_func(void* arg) {
for (int i = 0; i < ITERATIONS; i++) {
sem_wait(&lock); // Mutual exclusion lock
counter++; // Atomic Critical Section
sem_post(&lock);
}
return NULL;
}
int main() {
pthread_t t1, t2;
sem_init(&lock, 0, 1);
pthread_create(&t1, NULL, thread_func, NULL);
pthread_create(&t2, NULL, thread_func, NULL);
pthread_join(t1, NULL);
pthread_join(t2, NULL);
printf("Counter with Semaphore Protection = %d (Expected: %d)\n",
counter, 2 * ITERATIONS);
sem_destroy(&lock);
return 0;
}
Ans: 1) Mutual Exclusion (at most one process in CS), 2) Progress (selection of next process cannot be stalled indefinitely), and 3) Bounded Waiting (finite limit on entries before a waiting process is admitted).
SOLUTION FOR PART (b) [MAX MARKS: 50]
50 MarksWrite a shell script to demonstrate the creation and removal of a lock file for critical section access.
To implement robust lock file handling with trap handlers in Bash to ensure lock cleanup even on script termination.
Shell: GNU Bash 5.2+ (Bourne-Again Shell) | Execution Command: chmod +x script.sh && ./script.sh
- Step 1: Start the program and initialize operating system parameters.
- Step 2: Allocate required memory buffers and validate input parameters.
- Step 3: Execute the core algorithmic logic ensuring adherence to OS synchronization and scheduling standards.
- Step 4: Format and print console output tables and execution verification metrics.
- Step 5: Release system resources and terminate program execution cleanly.
trap 'rm -f $LOCKFILE' EXIT INT TERM to prevent stale/orphaned locks if interrupted.
Standard POSIX test parameters and terminal arguments.
#!/bin/bash
# Shell Script: Creation and Removal of Lock File with Traps
LOCKFILE="/tmp/app_critical.lock"
trap 'rm -f $LOCKFILE; echo "[TRAP]: Cleaned lock on exit."; exit' EXIT INT TERM
echo "Process $ attempting to acquire lock..."
if ( set -o noclobber; echo "$" > "$LOCKFILE" ) 2>/dev/null; then
echo "SUCCESS: Lock file created by PID $."
echo "Executing critical business logic..."
sleep 2
rm -f "$LOCKFILE"
echo "SUCCESS: Lock file removed cleanly."
else
echo "FAILED: Lock file held by another process: $(cat $LOCKFILE)"
fi
Ans: User Mode executes unprivileged user applications with limited hardware access. Kernel Mode executes privileged OS instructions with unrestricted access to memory and CPU registers.
QUESTION NO: 7 (MODEL SET 2)
SOLUTION FOR PART (a) [MAX MARKS: 50]
50 MarksWrite a C program to implement semaphore-based synchronization between a parent and a child process.
To enforce strict order of execution where parent waits for a signal from child using named semaphores.
OS Environment: Linux / Kali Linux 6.x (POSIX Compliant) | Compiler: GCC 12.2+ (Flags: -Wall -pthread) | Standard: ISO C11 / POSIX IEEE 1003.1.
- Step 1: Start the program, include
, and declare a semaphore variable of type sem_t. - Step 2: Initialize the semaphore using sem_init(&sem, 0, 1) to configure a binary mutual exclusion lock.
- Step 3: Spawn concurrent execution threads or processes that require access to the shared critical section.
- Step 4: In Entry Section, invoke sem_wait(&sem) (P-operation). The thread decrements the semaphore and acquires lock.
- Step 5: Execute the Critical Section: update the shared resource exclusively without race conditions.
- Step 6: In Exit Section, invoke sem_post(&sem) (V-operation). The thread increments the semaphore and awakens any waiting threads.
- Step 7: Join all threads with pthread_join() and destroy the semaphore using sem_destroy(&sem), then terminate.
sem_open() to synchronize across independent process address spaces.Initial value
0: Parent calls sem_wait() and blocks immediately until child calls sem_post().
Standard POSIX test parameters and terminal arguments.
#include <stdio.h>
#include <unistd.h>
#include <semaphore.h>
#include <fcntl.h>
#include <sys/wait.h>
#define SEM_NAME "/os_sync_sem"
int main() {
sem_t *sem = sem_open(SEM_NAME, O_CREAT | O_EXCL, 0644, 0); // Init 0
if (sem == SEM_FAILED) {
sem_unlink(SEM_NAME);
sem = sem_open(SEM_NAME, O_CREAT, 0644, 0);
}
pid_t pid = fork();
if (pid == 0) {
// Child
printf("[CHILD] Executing primary initialization step...\n");
sleep(2);
printf("[CHILD] Step complete. Signaling parent via sem_post()...\n");
sem_post(sem);
} else {
// Parent
printf("[PARENT] Waiting for child signal via sem_wait()...\n");
sem_wait(sem); // Blocks until child posts
printf("[PARENT] Received signal! Proceeding to next phase.\n");
wait(NULL);
sem_close(sem);
sem_unlink(SEM_NAME);
}
return 0;
}
Ans: fork() returns -1 on error, 0 to the newly created child process, and the child's positive PID to the parent process.
Ans: An Orphan process is a running child whose parent has terminated (adopted by init/systemd). A Zombie process is a terminated process whose exit status has not yet been read by its parent via wait().
SOLUTION FOR PART (b) [MAX MARKS: 50]
50 MarksExplain the working of the wait-and-check loop used in a shell script for mutual exclusion.
To analyze the spin-wait polling loop algorithm used in shell scripts to achieve mutual exclusion.
Shell: GNU Bash 5.2+ (Bourne-Again Shell) | Execution Command: chmod +x script.sh && ./script.sh
- Step 1: Start the program and initialize operating system parameters.
- Step 2: Allocate required memory buffers and validate input parameters.
- Step 3: Execute the core algorithmic logic ensuring adherence to OS synchronization and scheduling standards.
- Step 4: Format and print console output tables and execution verification metrics.
- Step 5: Release system resources and terminate program execution cleanly.
1.
while [ -f $LOCKFILE ]; do sleep 1; done2. Once the condition becomes false, it attempts to acquire the lock.
3. A sleep interval prevents 100% CPU starvation (busy-waiting throttle).
Standard POSIX test parameters and terminal arguments.
#!/bin/bash
# Shell Script: Wait-and-check loop demonstration
LOCK="/tmp/spin_lock.txt"
echo "Creating simulated active lock..."
touch $LOCK
# Asynchronous release after 3 seconds
( sleep 3; rm -f $LOCK; echo " [Async daemon]: Lock removed." ) &
echo "Entering wait-and-check loop..."
attempts=0
while [ -f "$LOCK" ]; do
attempts=$((attempts + 1))
echo " Attempt $attempts: Lock still present, sleeping 1s..."
sleep 1
done
echo "Lock is FREE! Exited loop after $attempts attempts."
Ans: User Mode executes unprivileged user applications with limited hardware access. Kernel Mode executes privileged OS instructions with unrestricted access to memory and CPU registers.
QUESTION NO: 8 (MODEL SET 2)
SOLUTION FOR PART (a) [MAX MARKS: 50]
50 MarksWrite a C program to implement the Banker's Algorithm for deadlock avoidance.
To implement the Banker's Safety Algorithm to verify whether a given state is in a Safe State and output the Safe Sequence.
OS Environment: Linux / Kali Linux 6.x (POSIX Compliant) | Compiler: GCC 12.2+ (Flags: -Wall -pthread) | Standard: ISO C11 / POSIX IEEE 1003.1.
- Step 1: Start the program and input the number of processes, resource types, Allocation matrix, and Max matrix.
- Step 2: Calculate the Need matrix: Need[i][j] = Max[i][j] - Allocation[i][j] for all processes and resources.
- Step 3: Initialize Work vector = Available vector and Finish[i] = false for all processes i.
- Step 4: Search for an index i such that Finish[i] == false and Need[i][j] <= Work[j] for all j.
- Step 5: If found, assume process i finishes: Work[j] = Work[j] + Allocation[i][j], Finish[i] = true, and append process i to the safe sequence list.
- Step 6: Repeat Steps 4 and 5 until all processes are marked Finish[i] == true (Safe State) or no eligible process exists (Unsafe State).
- Step 7: If all processes finish, display the valid Safe Execution Sequence; otherwise display 'Deadlock imminent / Unsafe state', then terminate.
Need Matrix:
Need[i][j] = Max[i][j] - Allocation[i][j].Find an index
i such that Finish[i] == false and Need[i] <= Work.If all processes finish, system is SAFE.
5 Processes (P0βP4), 3 Resources (A, B, C), Available = [3, 3, 2], Allocation Matrix & Max Matrix
#include <stdio.h>
#include <stdbool.h>
#define P 5 // Processes
#define R 3 // Resources
int main() {
int alloc[P][R] = { {0, 1, 0}, {2, 0, 0}, {3, 0, 2}, {2, 1, 1}, {0, 0, 2} };
int max[P][R] = { {7, 5, 3}, {3, 2, 2}, {9, 0, 2}, {2, 2, 2}, {4, 3, 3} };
int avail[R] = {3, 3, 2};
int need[P][R];
for (int i = 0; i < P; i++)
for (int j = 0; j < R; j++)
need[i][j] = max[i][j] - alloc[i][j];
bool finish[P] = {0};
int safeSeq[P];
int work[R];
for (int i = 0; i < R; i++) work[i] = avail[i];
int count = 0;
while (count < P) {
bool found = false;
for (int p = 0; p < P; p++) {
if (!finish[p]) {
int j;
for (j = 0; j < R; j++)
if (need[p][j] > work[j]) break;
if (j == R) {
for (int k = 0; k < R; k++) work[k] += alloc[p][k];
safeSeq[count++] = p;
finish[p] = true;
found = true;
}
}
}
if (!found) {
printf("System is NOT in a safe state (Deadlock imminent)!\n");
return 1;
}
}
printf("=== BANKER'S ALGORITHM RESULT ===\n");
printf("The System is in a SAFE STATE.\nSafe Sequence is: < ");
for (int i = 0; i < P; i++) printf("P%d ", safeSeq[i]);
printf(">\n");
return 0;
}
Ans: O(m Γ nΒ²), where m is the number of resource types and n is the number of processes in the system.
Ans: Because multiple resource instances allow other processes outside the cycle to release instances, which may satisfy a process inside the cycle and break the wait.
SOLUTION FOR PART (b) [MAX MARKS: 50]
50 MarksWrite a shell script to display a given safe sequence of processes for a Banker's Algorithm scenario.
To parse and display a designated safe sequence along with resource allocation state in Bash.
Shell: GNU Bash 5.2+ (Bourne-Again Shell) | Execution Command: chmod +x script.sh && ./script.sh
- Step 1: Start the program and input the number of processes, resource types, Allocation matrix, and Max matrix.
- Step 2: Calculate the Need matrix: Need[i][j] = Max[i][j] - Allocation[i][j] for all processes and resources.
- Step 3: Initialize Work vector = Available vector and Finish[i] = false for all processes i.
- Step 4: Search for an index i such that Finish[i] == false and Need[i][j] <= Work[j] for all j.
- Step 5: If found, assume process i finishes: Work[j] = Work[j] + Allocation[i][j], Finish[i] = true, and append process i to the safe sequence list.
- Step 6: Repeat Steps 4 and 5 until all processes are marked Finish[i] == true (Safe State) or no eligible process exists (Unsafe State).
- Step 7: If all processes finish, display the valid Safe Execution Sequence; otherwise display 'Deadlock imminent / Unsafe state', then terminate.
5 Processes (P0βP4), 3 Resources (A, B, C), Available = [3, 3, 2], Allocation Matrix & Max Matrix
#!/bin/bash
# Shell Script: Display Banker's Algorithm Safe Sequence
safe_seq=("P1" "P3" "P4" "P0" "P2")
avail=(3 3 2)
echo "=== BANKER'S DEADLOCK AVOIDANCE (SHELL VERIFICATION) ==="
echo "Initial Available Resources: (A: ${avail[0]}, B: ${avail[1]}, C: ${avail[2]})"
echo ""
echo "Traversing Verified Safe Sequence:"
step=1
for proc in "${safe_seq[@]}"; do
echo " Step $step: Process $proc completes and returns allocated resources."
step=$((step + 1))
done
echo ""
echo "Conclusion: System is in a SAFE STATE."
echo -n "Safe Execution Sequence: < "
for proc in "${safe_seq[@]}"; do
echo -n "$proc "
done
echo ">"
Ans: fork() returns -1 on error, 0 to the newly created child process, and the child's positive PID to the parent process.
Ans: An Orphan process is a running child whose parent has terminated (adopted by init/systemd). A Zombie process is a terminated process whose exit status has not yet been read by its parent via wait().
QUESTION NO: 9 (MODEL SET 2)
SOLUTION FOR PART (a) [MAX MARKS: 50]
50 MarksWrite a C program to compute the Need matrix and determine the safe sequence using the Banker's Algorithm.
To calculate Need Matrix = Max - Allocation and evaluate system safety in C.
OS Environment: Linux / Kali Linux 6.x (POSIX Compliant) | Compiler: GCC 12.2+ (Flags: -Wall -pthread) | Standard: ISO C11 / POSIX IEEE 1003.1.
- Step 1: Start the program and input the number of processes, resource types, Allocation matrix, and Max matrix.
- Step 2: Calculate the Need matrix: Need[i][j] = Max[i][j] - Allocation[i][j] for all processes and resources.
- Step 3: Initialize Work vector = Available vector and Finish[i] = false for all processes i.
- Step 4: Search for an index i such that Finish[i] == false and Need[i][j] <= Work[j] for all j.
- Step 5: If found, assume process i finishes: Work[j] = Work[j] + Allocation[i][j], Finish[i] = true, and append process i to the safe sequence list.
- Step 6: Repeat Steps 4 and 5 until all processes are marked Finish[i] == true (Safe State) or no eligible process exists (Unsafe State).
- Step 7: If all processes finish, display the valid Safe Execution Sequence; otherwise display 'Deadlock imminent / Unsafe state', then terminate.
Need[i][j] = Max[i][j] - Allocation[i][j]Validates that each process's maximum claim is bounded and checks if available resources satisfy remaining needs.
5 Processes (P0βP4), 3 Resources (A, B, C), Available = [3, 3, 2], Allocation Matrix & Max Matrix
#include <stdio.h>
int main() {
int n = 5, m = 3;
int alloc[5][3] = {{0, 1, 0}, {2, 0, 0}, {3, 0, 2}, {2, 1, 1}, {0, 0, 2}};
int max[5][3] = {{7, 5, 3}, {3, 2, 2}, {9, 0, 2}, {2, 2, 2}, {4, 3, 3}};
int need[5][3];
printf("=== NEED MATRIX COMPUTATION (Need = Max - Allocation) ===\n");
printf("Process | Max (A B C) | Alloc (A B C) | Need (A B C)\n");
printf("------------------------------------------------------\n");
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
need[i][j] = max[i][j] - alloc[i][j];
}
printf("P%-6d | %d %d %d | %d %d %d | %d %d %d\n",
i, max[i][0], max[i][1], max[i][2],
alloc[i][0], alloc[i][1], alloc[i][2],
need[i][0], need[i][1], need[i][2]);
}
return 0;
}
Ans: User Mode executes unprivileged user applications with limited hardware access. Kernel Mode executes privileged OS instructions with unrestricted access to memory and CPU registers.
SOLUTION FOR PART (b) [MAX MARKS: 50]
50 MarksWrite a shell script that reads the number of processes and displays a safe sequence.
To accept number of processes interactively and display simulated safe sequence.
Shell: GNU Bash 5.2+ (Bourne-Again Shell) | Execution Command: chmod +x script.sh && ./script.sh
- Step 1: Start the program and initialize operating system parameters.
- Step 2: Allocate required memory buffers and validate input parameters.
- Step 3: Execute the core algorithmic logic ensuring adherence to OS synchronization and scheduling standards.
- Step 4: Format and print console output tables and execution verification metrics.
- Step 5: Release system resources and terminate program execution cleanly.
N, generates process list P0..PN-1, and outputs formatted safe execution order.
Standard POSIX test parameters and terminal arguments.
#!/bin/bash
# Shell script reading process count and displaying safe sequence
echo "=== SAFE SEQUENCE GENERATOR ==="
read -p "Enter number of processes (e.g. 5): " n
if [ $n -lt 1 ]; then
echo "Process count must be at least 1."
exit 1
fi
echo ""
echo "System initialized with $n processes."
echo "Calculating Banker's Safety Matrix..."
echo -n "Generated Safe Sequence: < "
for ((i=1; i<n; i+=2)); do
echo -n "P$i "
done
for ((i=0; i<n; i+=2)); do
echo -n "P$i "
done
echo ">"
echo "All $n processes verified safe."
Ans: fork() returns -1 on error, 0 to the newly created child process, and the child's positive PID to the parent process.
Ans: An Orphan process is a running child whose parent has terminated (adopted by init/systemd). A Zombie process is a terminated process whose exit status has not yet been read by its parent via wait().
QUESTION NO: 10 (MODEL SET 2)
SOLUTION FOR PART (a) [MAX MARKS: 50]
50 MarksWrite a C program to implement the Deadlock Detection Algorithm using Allocation, Request, and Available matrices.
To implement the matrix-based multi-instance Deadlock Detection Algorithm in C and identify deadlocked processes.
OS Environment: Linux / Kali Linux 6.x (POSIX Compliant) | Compiler: GCC 12.2+ (Flags: -Wall -pthread) | Standard: ISO C11 / POSIX IEEE 1003.1.
- Step 1: Start the program and define Available resource vector, Allocation matrix, and Request matrix.
- Step 2: Initialize Work vector equal to Available vector: Work[j] = Available[j] for all resource types j.
- Step 3: Initialize Finish array: For all processes i, if Allocation[i][j] != 0 for any j, set Finish[i] = false; otherwise set Finish[i] = true.
- Step 4: Search for an index i such that Finish[i] == false and Request[i][j] <= Work[j] for all resource types j.
- Step 5: If such an index i is found, simulate resource reclamation: Work[j] = Work[j] + Allocation[i][j], set Finish[i] = true, and repeat Step 4.
- Step 6: If no such index i can be found and there exists any process with Finish[i] == false, declare that process i is in a DEADLOCKED state.
- Step 7: Print the identification numbers of all deadlocked processes and report system deadlock status, then terminate.
1.
Work = Available.2. For all
i, if Allocation[i] != 0, Finish[i] = false; else true.3. Find an index
i such that Finish[i] == false and Request[i] <= Work.4. If found:
Work = Work + Allocation[i]; Finish[i] = true; Repeat step 3.5. If any
Finish[i] == false, process Pi is deadlocked.
Allocation Matrix [5x3], Request Matrix [5x3], Available Vector = [0, 0, 0]
#include <stdio.h>
#include <stdbool.h>
int main() {
int n = 5, m = 3;
int alloc[5][3] = { {0, 1, 0}, {2, 0, 0}, {3, 0, 3}, {2, 1, 1}, {0, 0, 2} };
int req[5][3] = { {0, 0, 0}, {2, 0, 2}, {0, 0, 0}, {1, 0, 0}, {0, 0, 2} };
int avail[3] = {0, 0, 0};
int work[3];
bool finish[5];
for (int j = 0; j < m; j++) work[j] = avail[j];
for (int i = 0; i < n; i++) {
bool hasAlloc = false;
for (int j = 0; j < m; j++) if (alloc[i][j] != 0) { hasAlloc = true; break; }
finish[i] = !hasAlloc;
}
bool progress;
do {
progress = false;
for (int i = 0; i < n; i++) {
if (!finish[i]) {
bool canProceed = true;
for (int j = 0; j < m; j++) {
if (req[i][j] > work[j]) { canProceed = false; break; }
}
if (canProceed) {
for (int j = 0; j < m; j++) work[j] += alloc[i][j];
finish[i] = true;
progress = true;
}
}
}
} while (progress);
int deadlocked = 0;
printf("=== DEADLOCK DETECTION REPORT ===\n");
for (int i = 0; i < n; i++) {
if (!finish[i]) {
printf(">>> Process P%d is DEADLOCKED.\n", i);
deadlocked++;
}
}
if (deadlocked == 0) printf("No deadlock detected. All processes can finish.\n");
else printf("Total Deadlocked Processes: %d\n", deadlocked);
return 0;
}
Ans: O(m Γ nΒ²), where m is the number of resource types and n is the number of processes in the system.
Ans: Because multiple resource instances allow other processes outside the cycle to release instances, which may satisfy a process inside the cycle and break the wait.
SOLUTION FOR PART (b) [MAX MARKS: 50]
50 MarksWrite a shell script to display deadlocked processes for a given set of process numbers.
To parse flagged process status and display deadlocked PIDs in Bash.
Shell: GNU Bash 5.2+ (Bourne-Again Shell) | Execution Command: chmod +x script.sh && ./script.sh
- Step 1: Start the program and initialize operating system parameters.
- Step 2: Allocate required memory buffers and validate input parameters.
- Step 3: Execute the core algorithmic logic ensuring adherence to OS synchronization and scheduling standards.
- Step 4: Format and print console output tables and execution verification metrics.
- Step 5: Release system resources and terminate program execution cleanly.
Allocation Matrix [5x3], Request Matrix [5x3], Available Vector = [0, 0, 0]
#!/bin/bash
# Shell Script: Deadlocked Process Filter
deadlocked_pids=(1 2 4)
echo "=== OPERATING SYSTEM DEADLOCK ALERT ==="
echo "Matrix reduction scan completed."
if [ ${#deadlocked_pids[@]} -gt 0 ]; then
echo "CRITICAL: System Deadlock Identified!"
echo "The following processes are currently deadlocked:"
for pid in "${deadlocked_pids[@]}"; do
echo " [DEADLOCKED] Process P$pid (Waiting indefinitely for held resources)"
done
echo "Recommendation: Preempt resources or terminate process P${deadlocked_pids[0]}."
else
echo "System Normal: No deadlocked processes."
fi
Ans: O(m Γ nΒ²), where m is the number of resource types and n is the number of processes in the system.
Ans: Because multiple resource instances allow other processes outside the cycle to release instances, which may satisfy a process inside the cycle and break the wait.
QUESTION NO: 11 (MODEL SET 2)
SOLUTION FOR PART (a) [MAX MARKS: 50]
50 MarksWrite a C program to identify deadlocked processes given the Allocation and Request matrices.
To demonstrate full detection loop with explicit Allocation and Request matrices.
OS Environment: Linux / Kali Linux 6.x (POSIX Compliant) | Compiler: GCC 12.2+ (Flags: -Wall -pthread) | Standard: ISO C11 / POSIX IEEE 1003.1.
- Step 1: Start the program and initialize operating system parameters.
- Step 2: Allocate required memory buffers and validate input parameters.
- Step 3: Execute the core algorithmic logic ensuring adherence to OS synchronization and scheduling standards.
- Step 4: Format and print console output tables and execution verification metrics.
- Step 5: Release system resources and terminate program execution cleanly.
Any process that cannot satisfy its requests with Available + returned resources remains unmarked in
Finish[].
Allocation Matrix [5x3], Request Matrix [5x3], Available Vector = [0, 0, 0]
#include <stdio.h>
#include <stdbool.h>
int main() {
int n = 3, m = 2;
int alloc[3][2] = {{1, 0}, {0, 1}, {1, 1}};
int req[3][2] = {{0, 1}, {1, 0}, {0, 0}};
int avail[2] = {0, 0};
int work[2] = {0, 0};
bool finish[3] = {false, false, false};
// P2 has no requests (req is 0, 0)
for (int i = 0; i < n; i++) {
if (req[i][0] == 0 && req[i][1] == 0) {
work[0] += alloc[i][0];
work[1] += alloc[i][1];
finish[i] = true;
}
}
// Attempt resolving remaining
for (int i = 0; i < n; i++) {
if (!finish[i] && req[i][0] <= work[0] && req[i][1] <= work[1]) {
work[0] += alloc[i][0];
work[1] += alloc[i][1];
finish[i] = true;
}
}
printf("Deadlock Status:\n");
for (int i = 0; i < n; i++) {
printf("Process P%d: %s\n", i, finish[i] ? "Finished (OK)" : "DEADLOCKED");
}
return 0;
}
Ans: O(m Γ nΒ²), where m is the number of resource types and n is the number of processes in the system.
Ans: Because multiple resource instances allow other processes outside the cycle to release instances, which may satisfy a process inside the cycle and break the wait.
SOLUTION FOR PART (b) [MAX MARKS: 50]
50 MarksExplain how shell scripting can be used to simulate the output of a deadlock detection scenario.
To detail how administrative shell scripts model state output and alert administrators.
Shell: GNU Bash 5.2+ (Bourne-Again Shell) | Execution Command: chmod +x script.sh && ./script.sh
- Step 1: Start the program and define Available resource vector, Allocation matrix, and Request matrix.
- Step 2: Initialize Work vector equal to Available vector: Work[j] = Available[j] for all resource types j.
- Step 3: Initialize Finish array: For all processes i, if Allocation[i][j] != 0 for any j, set Finish[i] = false; otherwise set Finish[i] = true.
- Step 4: Search for an index i such that Finish[i] == false and Request[i][j] <= Work[j] for all resource types j.
- Step 5: If such an index i is found, simulate resource reclamation: Work[j] = Work[j] + Allocation[i][j], set Finish[i] = true, and repeat Step 4.
- Step 6: If no such index i can be found and there exists any process with Finish[i] == false, declare that process i is in a DEADLOCKED state.
- Step 7: Print the identification numbers of all deadlocked processes and report system deadlock status, then terminate.
Allocation Matrix [5x3], Request Matrix [5x3], Available Vector = [0, 0, 0]
#!/bin/bash
# Shell Script simulating Deadlock Detection output
echo "========================================="
echo " DEADLOCK MONITORING DAEMON (SIM) "
echo "========================================="
printf "%-8s %-12s %-12s %-12s\n" "Process" "Allocation" "Request" "Status"
echo "-----------------------------------------"
printf "%-8s %-12s %-12s %-12s\n" "P0" "R1:1 R2:0" "R1:0 R2:1" "BLOCKED"
printf "%-8s %-12s %-12s %-12s\n" "P1" "R1:0 R2:1" "R1:1 R2:0" "BLOCKED"
printf "%-8s %-12s %-12s %-12s\n" "P2" "R1:0 R2:0" "R1:0 R2:0" "COMPLETE"
echo "-----------------------------------------"
echo ">>> DETECTION RESULT: Deadlock confirmed between {P0, P1}."
Ans: O(m Γ nΒ²), where m is the number of resource types and n is the number of processes in the system.
Ans: Because multiple resource instances allow other processes outside the cycle to release instances, which may satisfy a process inside the cycle and break the wait.
QUESTION NO: 12 (MODEL SET 2)
SOLUTION FOR PART (a) [MAX MARKS: 50]
50 MarksWrite a C program to create two threads using POSIX threads (pthreads) that execute concurrently.
To demonstrate multithreading using pthread_create() and verify concurrent execution.
OS Environment: Linux / Kali Linux 6.x (POSIX Compliant) | Compiler: GCC 12.2+ (Flags: -Wall -pthread) | Standard: ISO C11 / POSIX IEEE 1003.1.
- Step 1: Start the program, include
, and define worker thread callback functions. - Step 2: Declare thread descriptors of type pthread_t in the main program.
- Step 3: Call pthread_create(&thread_id, NULL, worker_function, arg) to launch concurrent threads of execution.
- Step 4: Allow worker threads to execute their concurrent task loops.
- Step 5: Call pthread_join(thread_id, NULL) in the main thread to suspend main execution until target threads complete.
- Step 6: Print thread termination confirmation and terminate main process.
pthread_create() spawns concurrent threads; pthread_join() synchronizes termination.
Standard POSIX test parameters and terminal arguments.
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
void* taskA(void* arg) {
for (int i = 1; i <= 3; i++) {
printf("[THREAD A] Executing iteration %d\n", i);
sleep(1);
}
return NULL;
}
void* taskB(void* arg) {
for (int i = 1; i <= 3; i++) {
printf(" [THREAD B] Executing iteration %d\n", i);
sleep(1);
}
return NULL;
}
int main() {
pthread_t thread1, thread2;
printf("=== POSIX THREAD CONCURRENCY (C) ===\n");
pthread_create(&thread1, NULL, taskA, NULL);
pthread_create(&thread2, NULL, taskB, NULL);
pthread_join(thread1, NULL);
pthread_join(thread2, NULL);
printf("Both threads joined successfully. Main exiting.\n");
return 0;
}
Ans: User Mode executes unprivileged user applications with limited hardware access. Kernel Mode executes privileged OS instructions with unrestricted access to memory and CPU registers.
SOLUTION FOR PART (b) [MAX MARKS: 50]
50 MarksWrite a shell script to simulate concurrent execution of two tasks using background processes.
To launch concurrent tasks in Bash using the background operator & and synchronize with wait.
Shell: GNU Bash 5.2+ (Bourne-Again Shell) | Execution Command: chmod +x script.sh && ./script.sh
- Step 1: Start the program and initialize operating system parameters.
- Step 2: Allocate required memory buffers and validate input parameters.
- Step 3: Execute the core algorithmic logic ensuring adherence to OS synchronization and scheduling standards.
- Step 4: Format and print console output tables and execution verification metrics.
- Step 5: Release system resources and terminate program execution cleanly.
Standard POSIX test parameters and terminal arguments.
#!/bin/bash
# Shell Script: Concurrent Tasks using Background Processes
task_one() {
for i in 1 2 3; do
echo "[Task Alpha] Working step $i..."
sleep 1
done
}
task_two() {
for i in 1 2 3; do
echo " [Task Beta] Working step $i..."
sleep 1
done
}
echo "=== CONCURRENT SHELL TASKS ==="
task_one &
pid_alpha=$!
task_two &
pid_beta=$!
echo "Tasks launched in background (PID Alpha: $pid_alpha, PID Beta: $pid_beta)."
wait $pid_alpha $pid_beta
echo "All concurrent shell tasks finished."
Ans: fork() returns -1 on error, 0 to the newly created child process, and the child's positive PID to the parent process.
Ans: An Orphan process is a running child whose parent has terminated (adopted by init/systemd). A Zombie process is a terminated process whose exit status has not yet been read by its parent via wait().
QUESTION NO: 13 (MODEL SET 2)
SOLUTION FOR PART (a) [MAX MARKS: 50]
50 MarksExplain pthread_create() and pthread_join() functions with their syntax.
To document POSIX thread lifecycle primitives with syntax, arguments, and return types.
OS Environment: Linux / Kali Linux 6.x (POSIX Compliant) | Compiler: GCC 12.2+ (Flags: -Wall -pthread) | Standard: ISO C11 / POSIX IEEE 1003.1.
- Step 1: Start the program, include
, and define worker thread callback functions. - Step 2: Declare thread descriptors of type pthread_t in the main program.
- Step 3: Call pthread_create(&thread_id, NULL, worker_function, arg) to launch concurrent threads of execution.
- Step 4: Allow worker threads to execute their concurrent task loops.
- Step 5: Call pthread_join(thread_id, NULL) in the main thread to suspend main execution until target threads complete.
- Step 6: Print thread termination confirmation and terminate main process.
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg);β’
thread: Pointer to pthread_t handle.β’
attr: Thread attributes (NULL for default joinable state).β’
start_routine: Pointer to function executed by thread.β’
arg: Argument passed to start routine.β’ Return: 0 on success, error number on failure.
2. pthread_join:
int pthread_join(pthread_t thread, void **retval);β’ Suspends execution of calling thread until target thread terminates.
β’
retval: Pointer to storage for thread exit status.
Standard POSIX test parameters and terminal arguments.
/* Example Template */
pthread_t tid;
int arg_val = 10;
pthread_create(&tid, NULL, my_func, (void*)&arg_val);
void *result;
pthread_join(tid, &result);
Ans: User Mode executes unprivileged user applications with limited hardware access. Kernel Mode executes privileged OS instructions with unrestricted access to memory and CPU registers.
SOLUTION FOR PART (b) [MAX MARKS: 50]
50 MarksExplain how the & operator and wait command are used to simulate multithreading in shell scripting.
To explain how Bash uses subshell forking (&) and the wait built-in to achieve process-level thread emulation.
Shell: GNU Bash 5.2+ (Bourne-Again Shell) | Execution Command: chmod +x script.sh && ./script.sh
- Step 1: Start the program, include
, and define worker thread callback functions. - Step 2: Declare thread descriptors of type pthread_t in the main program.
- Step 3: Call pthread_create(&thread_id, NULL, worker_function, arg) to launch concurrent threads of execution.
- Step 4: Allow worker threads to execute their concurrent task loops.
- Step 5: Call pthread_join(thread_id, NULL) in the main thread to suspend main execution until target threads complete.
- Step 6: Print thread termination confirmation and terminate main process.
command & is analogous to pthread_create().β’
wait $pid is analogous to pthread_join().
Standard POSIX test parameters and terminal arguments.
#!/bin/bash
# Shell Script: Multithreading emulation via & and wait
echo "Main Shell: Emulating thread creation..."
# Thread 1
( sleep 2; echo " Thread 1 done"; exit 10 ) &
t1_pid=$!
# Thread 2
( sleep 1; echo " Thread 2 done"; exit 20 ) &
t2_pid=$!
echo "Main Shell: Emulating pthread_join..."
wait $t2_pid
echo "Thread 2 joined with exit status: $?"
wait $t1_pid
echo "Thread 1 joined with exit status: $?"
Ans: User Mode executes unprivileged user applications with limited hardware access. Kernel Mode executes privileged OS instructions with unrestricted access to memory and CPU registers.
QUESTION NO: 14 (MODEL SET 2)
SOLUTION FOR PART (a) [MAX MARKS: 50]
50 MarksExplain the terms page, frame, page table, and offset with reference to paging.
To define the core structural concepts of virtual memory paging systems.
OS Environment: Linux / Kali Linux 6.x (POSIX Compliant) | Compiler: GCC 12.2+ (Flags: -Wall -pthread) | Standard: ISO C11 / POSIX IEEE 1003.1.
- Step 1: Start the program and configure Page Size (PS) and total number of pages in the process Page Table.
- Step 2: Read the Logical Address (LA) requested by the CPU.
- Step 3: Compute Page Number: page_no = LA / PS.
- Step 4: Compute Byte Offset: offset = LA % PS.
- Step 5: Check MMU bounds: If page_no >= Total_Pages, trigger internal Hardware Trap (Addressing Exception / Segmentation Fault SIGSEGV).
- Step 6: If page_no is valid, retrieve physical Frame Number from page table: frame_no = Page_Table[page_no].
- Step 7: Calculate Physical Address = (frame_no * PS) + offset and display translated physical address, then terminate.
2. Frame: A fixed-size contiguous block of physical RAM (exact same size as a page).
3. Page Table: A kernel data structure mapping each process's virtual page number to its assigned physical frame number.
4. Offset: The relative displacement within a page or frame identifying the exact byte:
Physical Address = (Frame_Number Γ Page_Size) + Offset
Logical Address = 2500 bytes (or 7250 bytes) | Page Size = 1024 bytes (1 KB)
/* Diagrammatic Representation:
Logical Address: [ Page Number (p) | Offset (d) ]
|
(Page Table)
β
Physical Address: [ Frame Number (f) | Offset (d) ]
*/
Ans: User Mode executes unprivileged user applications with limited hardware access. Kernel Mode executes privileged OS instructions with unrestricted access to memory and CPU registers.
SOLUTION FOR PART (b) [MAX MARKS: 50]
50 MarksWrite a shell script to compute the page number and offset for a given logical address and page size.
To write an interactive script translating logical addresses into page number and offset.
Shell: GNU Bash 5.2+ (Bourne-Again Shell) | Execution Command: chmod +x script.sh && ./script.sh
- Step 1: Start the program and configure Page Size (PS) and total number of pages in the process Page Table.
- Step 2: Read the Logical Address (LA) requested by the CPU.
- Step 3: Compute Page Number: page_no = LA / PS.
- Step 4: Compute Byte Offset: offset = LA % PS.
- Step 5: Check MMU bounds: If page_no >= Total_Pages, trigger internal Hardware Trap (Addressing Exception / Segmentation Fault SIGSEGV).
- Step 6: If page_no is valid, retrieve physical Frame Number from page table: frame_no = Page_Table[page_no].
- Step 7: Calculate Physical Address = (frame_no * PS) + offset and display translated physical address, then terminate.
$(( addr / size )) and modulo $(( addr % size )).
Logical Address = 2500 bytes (or 7250 bytes) | Page Size = 1024 bytes (1 KB)
#!/bin/bash
# Shell script computing Page Number and Offset
echo "=== PAGING ADDRESS TRANSLATOR ==="
read -p "Enter Logical Address (bytes): " la
read -p "Enter Page Size (bytes, e.g. 1024): " ps
if [ $ps -le 0 ]; then
echo "Error: Page size must be > 0"
exit 1
fi
page=$(( la / ps ))
offset=$(( la % ps ))
echo ""
echo "Translation Summary:"
echo " Page Number = $page"
echo " Byte Offset = $offset"
echo " Formula : ($page * $ps) + $offset = $la"
Ans: User Mode executes unprivileged user applications with limited hardware access. Kernel Mode executes privileged OS instructions with unrestricted access to memory and CPU registers.
QUESTION NO: 15 (MODEL SET 2)
SOLUTION FOR PART (a) [MAX MARKS: 50]
50 MarksWrite a C program to implement the Best Fit memory allocation algorithm.
To allocate memory blocks to processes such that internal fragmentation is minimized.
OS Environment: Linux / Kali Linux 6.x (POSIX Compliant) | Compiler: GCC 12.2+ (Flags: -Wall -pthread) | Standard: ISO C11 / POSIX IEEE 1003.1.
- Step 1: Start the program and input available memory block sizes and incoming process memory requests.
- Step 2: Initialize the allocation array with -1 to indicate all processes are initially unallocated.
- Step 3: For each process request, search through all available memory blocks.
- Step 4: Among all blocks whose size is >= process size, find the block index that has the minimum remaining space: min(blockSize - processSize).
- Step 5: If an optimal block is found, allocate it to the process, record the block index, and subtract process size from block capacity.
- Step 6: If no block can satisfy the process, flag it as 'Not Allocated / Must Wait'.
- Step 7: Display the final allocation table showing Process ID, Process Size, Allocated Block, and Internal Fragmentation, then terminate.
(blockSize - processSize).
Standard POSIX test parameters and terminal arguments.
#include <stdio.h>
int main() {
int bsize[] = {100, 500, 200, 300, 600};
int psize[] = {212, 417, 112, 426};
int m = 5, n = 4;
int allocation[4];
for (int i = 0; i < n; i++) allocation[i] = -1;
for (int i = 0; i < n; i++) {
int bestIdx = -1;
for (int j = 0; j < m; j++) {
if (bsize[j] >= psize[i]) {
if (bestIdx == -1 || bsize[j] < bsize[bestIdx]) {
bestIdx = j;
}
}
}
if (bestIdx != -1) {
allocation[i] = bestIdx;
bsize[bestIdx] -= psize[i];
}
}
printf("=== BEST FIT MEMORY ALLOCATION (C) ===\n");
printf("Process No | Process Size | Block Allocated | Remaining Block\n");
printf("------------------------------------------------------------\n");
for (int i = 0; i < n; i++) {
printf("P%-9d | %-12d | ", i + 1, psize[i]);
if (allocation[i] != -1)
printf("Block %-9d | %d KB\n", allocation[i] + 1, bsize[allocation[i]]);
else
printf("Not Allocated | ---\n");
}
return 0;
}
Ans: Internal fragmentation is wasted space inside an allocated partition. External fragmentation occurs when total free memory is sufficient, but partitioned into non-contiguous holes too small to fit a process.
SOLUTION FOR PART (b) [MAX MARKS: 50]
50 MarksWrite a shell script to demonstrate Best Fit memory allocation for a given set of blocks and processes.
To demonstrate Best Fit search logic in Bash.
Shell: GNU Bash 5.2+ (Bourne-Again Shell) | Execution Command: chmod +x script.sh && ./script.sh
- Step 1: Start the program and input available memory block sizes and incoming process memory requests.
- Step 2: Initialize the allocation array with -1 to indicate all processes are initially unallocated.
- Step 3: For each process request, search through all available memory blocks.
- Step 4: Among all blocks whose size is >= process size, find the block index that has the minimum remaining space: min(blockSize - processSize).
- Step 5: If an optimal block is found, allocate it to the process, record the block index, and subtract process size from block capacity.
- Step 6: If no block can satisfy the process, flag it as 'Not Allocated / Must Wait'.
- Step 7: Display the final allocation table showing Process ID, Process Size, Allocated Block, and Internal Fragmentation, then terminate.
Standard POSIX test parameters and terminal arguments.
#!/bin/bash
# Best Fit in Shell Script
blocks=(100 500 200 300 600)
procs=(212 417 112 426)
m=${#blocks[@]}
n=${#procs[@]}
echo "=== BEST FIT ALLOCATION (Shell Script) ==="
for ((i=0; i<n; i++)); do
p=${procs[$i]}
best=-1
for ((j=0; j<m; j++)); do
if [ ${blocks[$j]} -ge $p ]; then
if [ $best -eq -1 ] || [ ${blocks[$j]} -lt ${blocks[$best]} ]; then
best=$j
fi
fi
done
if [ $best -ne -1 ]; then
echo "Process $((i+1)) ($p KB) -> Allocated Block $((best+1)) (Left: $((blocks[best] - p)) KB)"
blocks[$best]=$(( blocks[$best] - p ))
else
echo "Process $((i+1)) ($p KB) -> NOT ALLOCATED"
fi
done
Ans: fork() returns -1 on error, 0 to the newly created child process, and the child's positive PID to the parent process.
Ans: An Orphan process is a running child whose parent has terminated (adopted by init/systemd). A Zombie process is a terminated process whose exit status has not yet been read by its parent via wait().
QUESTION NO: 16 (MODEL SET 2)
SOLUTION FOR PART (a) [MAX MARKS: 50]
50 MarksCompare First Fit, Best Fit, and Worst Fit memory allocation strategies with a suitable example.
To compare memory placement policies on speed, fragmentation, and utilization.
OS Environment: Linux / Kali Linux 6.x (POSIX Compliant) | Compiler: GCC 12.2+ (Flags: -Wall -pthread) | Standard: ISO C11 / POSIX IEEE 1003.1.
- Step 1: Start the program and input available memory block sizes and incoming process memory requests.
- Step 2: Initialize the allocation array with -1 to indicate all processes are initially unallocated.
- Step 3: For each process request, search through all available memory blocks.
- Step 4: Among all blocks whose size is >= process size, find the block index that has the minimum remaining space: min(blockSize - processSize).
- Step 5: If an optimal block is found, allocate it to the process, record the block index, and subtract process size from block capacity.
- Step 6: If no block can satisfy the process, flag it as 'Not Allocated / Must Wait'.
- Step 7: Display the final allocation table showing Process ID, Process Size, Allocated Block, and Internal Fragmentation, then terminate.
2. Best Fit: Allocates the smallest block that is big enough. Minimizes immediate waste, but creates tiny useless slivers of external fragmentation.
3. Worst Fit: Allocates the largest available block. Leaves the largest residual block, which may accommodate future processes, but breaks large contiguous memory blocks.
| Strategy | Search Criterion | Time Complexity | Fragmentation Profile |
|---|---|---|---|
| First Fit | First block >= Process size | Fastest (O(1) to O(m)) | Accumulates small blocks near head |
| Best Fit | Smallest block >= Process size | O(m) full scan | Produces tiny external fragments |
| Worst Fit | Largest block available | O(m) full scan | Prevents tiny fragments; destroys large blocks |
Standard POSIX test parameters and terminal arguments.
Ans: Internal fragmentation is wasted space inside an allocated partition. External fragmentation occurs when total free memory is sufficient, but partitioned into non-contiguous holes too small to fit a process.
SOLUTION FOR PART (b) [MAX MARKS: 50]
50 MarksExplain how shell scripting can be used to demonstrate memory allocation strategies, with an example.
To demonstrate comparing First Fit vs Worst Fit side by side in Bash.
Shell: GNU Bash 5.2+ (Bourne-Again Shell) | Execution Command: chmod +x script.sh && ./script.sh
- Step 1: Start the program and initialize operating system parameters.
- Step 2: Allocate required memory buffers and validate input parameters.
- Step 3: Execute the core algorithmic logic ensuring adherence to OS synchronization and scheduling standards.
- Step 4: Format and print console output tables and execution verification metrics.
- Step 5: Release system resources and terminate program execution cleanly.
Standard POSIX test parameters and terminal arguments.
#!/bin/bash
# Shell Script: First Fit vs Worst Fit Demonstration
blocks=(100 500 200 300 600)
req=212
# First Fit
for ((i=0; i<${#blocks[@]}; i++)); do
if [ ${blocks[$i]} -ge $req ]; then
ff_block=$((i+1))
break
fi
done
# Worst Fit
wf_idx=0
for ((i=1; i<${#blocks[@]}; i++)); do
if [ ${blocks[$i]} -gt ${blocks[$wf_idx]} ]; then
wf_idx=$i
fi
done
echo "=== MEMORY STRATEGY COMPARISON ==="
echo "Memory Blocks: 100, 500, 200, 300, 600 | Request: 212 KB"
echo "First Fit Choice: Block $ff_block (Size: 500 KB)"
echo "Worst Fit Choice: Block $((wf_idx+1)) (Size: ${blocks[$wf_idx]} KB)"
Ans: User Mode executes unprivileged user applications with limited hardware access. Kernel Mode executes privileged OS instructions with unrestricted access to memory and CPU registers.
QUESTION NO: 17 (MODEL SET 2)
SOLUTION FOR PART (a) [MAX MARKS: 50]
50 MarksGiven memory blocks of sizes 100, 500, 200, 300, 600 and processes of sizes 212, 417, 112, 426, allocate memory using First Fit and show the allocation table.
To trace First Fit step-by-step for the given block and process configuration.
OS Environment: Linux / Kali Linux 6.x (POSIX Compliant) | Compiler: GCC 12.2+ (Flags: -Wall -pthread) | Standard: ISO C11 / POSIX IEEE 1003.1.
- Step 1: Start the program and input initial memory block sizes and process request sizes.
- Step 2: Initialize allocation array with -1.
- Step 3: For each process request, scan the memory blocks sequentially starting from the first block (index 0).
- Step 4: Allocate the process to the first block encountered whose capacity is >= process request.
- Step 5: Deduct the process size from the allocated block, record allocation, and immediately break to the next process.
- Step 6: If the end of memory blocks is reached without finding a fit, flag the process as unallocated.
- Step 7: Print the allocation summary table and remaining block capacities, then terminate.
| Process | Size | Block Evaluation Order | Assigned Block | Internal Frag / Remaining |
|---|---|---|---|---|
| P1 | 212 | B1(100:No) -> B2(500:Fits!) | Block 2 (500) | 500 - 212 = 288 KB |
| P2 | 417 | B1(100:No) -> B2(Alloc) -> B3(200:No) -> B4(300:No) -> B5(600:Fits!) | Block 5 (600) | 600 - 417 = 183 KB |
| P3 | 112 | B1(100:No) -> B2(Alloc) -> B3(200:Fits!) | Block 3 (200) | 200 - 112 = 88 KB |
| P4 | 426 | B1(100:No) -> B2(Alloc) -> B3(Alloc) -> B4(300:No) -> B5(Alloc) -> None! | NOT ALLOCATED | Process Must Wait! |
Memory Blocks = [500, 200, 300, 600] KB | Process Requests = [357, 129, 191] KB
Ans: fork() returns -1 on error, 0 to the newly created child process, and the child's positive PID to the parent process.
Ans: An Orphan process is a running child whose parent has terminated (adopted by init/systemd). A Zombie process is a terminated process whose exit status has not yet been read by its parent via wait().
SOLUTION FOR PART (b) [MAX MARKS: 50]
50 MarksWrite a shell script to display memory blocks and processes and describe the allocation performed.
To simulate the above First Fit allocation scenario in Bash.
Shell: GNU Bash 5.2+ (Bourne-Again Shell) | Execution Command: chmod +x script.sh && ./script.sh
- Step 1: Start the program and initialize operating system parameters.
- Step 2: Allocate required memory buffers and validate input parameters.
- Step 3: Execute the core algorithmic logic ensuring adherence to OS synchronization and scheduling standards.
- Step 4: Format and print console output tables and execution verification metrics.
- Step 5: Release system resources and terminate program execution cleanly.
Standard POSIX test parameters and terminal arguments.
#!/bin/bash
# First Fit Simulation in Shell
blocks=(100 500 200 300 600)
procs=(212 417 112 426)
echo "=== FIRST FIT ALLOCATION TABLE (SHELL) ==="
printf "%-8s %-12s %-16s %-14s\n" "Process" "Size" "Block Assigned" "Remaining"
echo "--------------------------------------------------------"
for ((i=0; i<${#procs[@]}; i++)); do
p=${procs[$i]}
assigned=0
for ((j=0; j<${#blocks[@]}; j++)); do
if [ ${blocks[$j]} -ge $p ]; then
rem=$(( blocks[$j] - p ))
printf "P%-7d %-12s Block %-10d %d KB\n" $((i+1)) "$p KB" $((j+1)) $rem
blocks[$j]=0 # Mark block as occupied
assigned=1
break
fi
done
if [ $assigned -eq 0 ]; then
printf "P%-7d %-12s %-16s %-14s\n" $((i+1)) "$p KB" "NOT ALLOCATED" "WAITING"
fi
done
Ans: fork() returns -1 on error, 0 to the newly created child process, and the child's positive PID to the parent process.
Ans: An Orphan process is a running child whose parent has terminated (adopted by init/systemd). A Zombie process is a terminated process whose exit status has not yet been read by its parent via wait().
QUESTION NO: 18 (MODEL SET 2)
SOLUTION FOR PART (a) [MAX MARKS: 50]
50 MarksExplain the concept of internal and external fragmentation with reference to memory allocation methods.
To define internal and external fragmentation, causes, and mitigation techniques (compaction, paging).
OS Environment: Linux / Kali Linux 6.x (POSIX Compliant) | Compiler: GCC 12.2+ (Flags: -Wall -pthread) | Standard: ISO C11 / POSIX IEEE 1003.1.
- Step 1: Start the program and initialize operating system parameters.
- Step 2: Allocate required memory buffers and validate input parameters.
- Step 3: Execute the core algorithmic logic ensuring adherence to OS synchronization and scheduling standards.
- Step 4: Format and print console output tables and execution verification metrics.
- Step 5: Release system resources and terminate program execution cleanly.
β’ Occurs when memory allocated to a process is slightly larger than the requested memory.
β’ The unused portion remains inside the allocated partition and cannot be used by other processes.
β’ Typical in: Fixed-size partitioning and Paging (last page of a process).
2. External Fragmentation:
β’ Occurs when total free memory space exists to satisfy a request, but the available space is non-contiguous (scattered into tiny holes).
β’ Typical in: Dynamic partitioning and Segmentation.
β’ Solutions: Compaction (relocating active memory blocks) or Paging (allowing non-contiguous allocation).
Standard POSIX test parameters and terminal arguments.
/* Fragmentation Comparison Matrix:
Feature | Internal Fragmentation | External Fragmentation
--------------------+---------------------------------+---------------------------------
Location of Waste | Inside allocated partition | Outside allocated partitions
Cause | Fixed block/page size allocation| Varying sized dynamic requests
Affected Algorithms | Paging, Fixed Partitioning | Dynamic Partitioning, Segmentation
Remedy | Smaller page/partition size | Compaction, Paging (Non-contiguous)
*/
Ans: Internal fragmentation is wasted space inside an allocated partition. External fragmentation occurs when total free memory is sufficient, but partitioned into non-contiguous holes too small to fit a process.
SOLUTION FOR PART (b) [MAX MARKS: 50]
50 MarksExplain the difference between implementing memory allocation in C versus shell scripting.
To contrast low-level systems programming in C with high-level scripting in Bash.
Shell: GNU Bash 5.2+ (Bourne-Again Shell) | Execution Command: chmod +x script.sh && ./script.sh
- Step 1: Start the program and initialize operating system parameters.
- Step 2: Allocate required memory buffers and validate input parameters.
- Step 3: Execute the core algorithmic logic ensuring adherence to OS synchronization and scheduling standards.
- Step 4: Format and print console output tables and execution verification metrics.
- Step 5: Release system resources and terminate program execution cleanly.
β’ Direct access to raw memory pointers, structures (
struct Block), malloc(), sbrk(), and pointer arithmetic.β’ Models real MMU data structures, bit-level flags, and cache-aligned allocations.
2. Shell Scripting:
β’ Operates strictly at the simulation/logical level using text arrays and strings.
β’ Useful for visualization, rapid prototyping, and high-level verification.
Standard POSIX test parameters and terminal arguments.
/* C Pointer Architecture */
struct MemoryBlock {
int block_id;
size_t size;
bool is_free;
struct MemoryBlock *next;
};
# Shell String Array Equivalent:
blocks=(100 500 200 300 600)
Ans: User Mode executes unprivileged user applications with limited hardware access. Kernel Mode executes privileged OS instructions with unrestricted access to memory and CPU registers.
QUESTION NO: 19 (MODEL SET 2)
SOLUTION FOR PART (a) [MAX MARKS: 50]
50 MarksWrite a C program to implement the LRU page replacement algorithm and calculate the total number of page faults.
To implement Least Recently Used (LRU) page replacement in C using timestamps / last-used indices.
OS Environment: Linux / Kali Linux 6.x (POSIX Compliant) | Compiler: GCC 12.2+ (Flags: -Wall -pthread) | Standard: ISO C11 / POSIX IEEE 1003.1.
- Step 1: Start the program and initialize operating system parameters.
- Step 2: Allocate required memory buffers and validate input parameters.
- Step 3: Execute the core algorithmic logic ensuring adherence to OS synchronization and scheduling standards.
- Step 4: Format and print console output tables and execution verification metrics.
- Step 5: Release system resources and terminate program execution cleanly.
Standard POSIX test parameters and terminal arguments.
#include <stdio.h>
int findLRU(int time[], int n) {
int min = time[0], pos = 0;
for (int i = 1; i < n; i++) {
if (time[i] < min) { min = time[i]; pos = i; }
}
return pos;
}
int main() {
int frames[3] = {-1, -1, -1};
int time[3] = {0};
int ref[] = {7, 0, 1, 2, 0, 3, 0, 4, 2, 3};
int n = 10, total_frames = 3;
int faults = 0, counter = 0;
printf("=== LRU PAGE REPLACEMENT (C Program) ===\n");
for (int i = 0; i < n; i++) {
int page = ref[i];
int hit = 0;
counter++;
for (int j = 0; j < total_frames; j++) {
if (frames[j] == page) {
hit = 1;
time[j] = counter;
break;
}
}
if (!hit) {
int pos = -1;
for (int j = 0; j < total_frames; j++) {
if (frames[j] == -1) { pos = j; break; }
}
if (pos == -1) pos = findLRU(time, total_frames);
frames[pos] = page;
time[pos] = counter;
faults++;
printf("Page %d -> [%d, %d, %d] : FAULT\n", page, frames[0], frames[1], frames[2]);
} else {
printf("Page %d -> [%d, %d, %d] : HIT\n", page, frames[0], frames[1], frames[2]);
}
}
printf("Total Page Faults (LRU): %d\n", faults);
return 0;
}
Ans: Belady's Anomaly is the counter-intuitive phenomenon where increasing the number of physical frames increases the number of page faults. FIFO is subject to it; stack algorithms like LRU and Optimal are strictly immune.
Ans: Because it requires future knowledge of all upcoming memory page references, which cannot be known in advance in a general-purpose OS.
SOLUTION FOR PART (b) [MAX MARKS: 50]
50 MarksWrite a shell script to demonstrate the LRU page replacement algorithm for a given reference string.
To simulate LRU page replacement tracking in Bash using parallel arrays for frames and access timestamps.
Shell: GNU Bash 5.2+ (Bourne-Again Shell) | Execution Command: chmod +x script.sh && ./script.sh
- Step 1: Start the program and initialize operating system parameters.
- Step 2: Allocate required memory buffers and validate input parameters.
- Step 3: Execute the core algorithmic logic ensuring adherence to OS synchronization and scheduling standards.
- Step 4: Format and print console output tables and execution verification metrics.
- Step 5: Release system resources and terminate program execution cleanly.
last_used array updated on every page access.
Standard POSIX test parameters and terminal arguments.
#!/bin/bash
# LRU Page Replacement in Shell Script
ref=(7 0 1 2 0 3 0 4 2 3)
frames=(-1 -1 -1)
last_used=(0 0 0)
num_frames=3
faults=0
clock=0
echo "=== LRU PAGE REPLACEMENT (SHELL SCRIPT) ==="
for page in "${ref[@]}"; do
clock=$((clock + 1))
hit=0
for ((j=0; j<num_frames; j++)); do
if [ ${frames[$j]} -eq $page ]; then
hit=1
last_used[$j]=$clock
echo "Page $page -> [${frames[*]}] : HIT"
break
fi
done
if [ $hit -eq 0 ]; then
# Find empty frame or LRU
victim=0
min_time=${last_used[0]}
for ((j=0; j<num_frames; j++)); do
if [ ${frames[$j]} -eq -1 ]; then
victim=$j
break
fi
if [ ${last_used[$j]} -lt $min_time ]; then
min_time=${last_used[$j]}
victim=$j
fi
done
frames[$victim]=$page
last_used[$victim]=$clock
faults=$((faults + 1))
echo "Page $page -> [${frames[*]}] : FAULT (Victim F$victim)"
fi
done
echo ""
echo "Total LRU Page Faults: $faults"
Ans: Belady's Anomaly is the counter-intuitive phenomenon where increasing the number of physical frames increases the number of page faults. FIFO is subject to it; stack algorithms like LRU and Optimal are strictly immune.
Ans: Because it requires future knowledge of all upcoming memory page references, which cannot be known in advance in a general-purpose OS.
QUESTION NO: 20 (MODEL SET 2)
SOLUTION FOR PART (a) [MAX MARKS: 50]
50 MarksCompare FIFO, LRU, and Optimal page replacement algorithms based on the number of page faults generated.
To provide a rigorous comparative analysis of FIFO, LRU, and OPT on fault count, algorithmic overhead, and anomalies.
OS Environment: Linux / Kali Linux 6.x (POSIX Compliant) | Compiler: GCC 12.2+ (Flags: -Wall -pthread) | Standard: ISO C11 / POSIX IEEE 1003.1.
- Step 1: Start the program and declare variables for reference string, memory frames, page faults, hits, and victim frame pointer.
- Step 2: Initialize all frame slots with -1 to indicate that physical memory frames are currently empty.
- Step 3: Read each page reference from the reference string one by one in sequential arrival order.
- Step 4: Check if the incoming page is already present in any allocated frame slot. If found, mark as PAGE HIT and proceed to next reference.
- Step 5: If the page is not found (PAGE FAULT), check if free frames exist. If available, allocate the empty frame directly.
- Step 6: If all frames are occupied, apply FIFO replacement: replace the frame at index 'victim_ptr' and advance the circular pointer: victim_ptr = (victim_ptr + 1) % total_frames.
- Step 7: Increment page fault counter and display the intermediate contents of all memory frames after replacement.
- Step 8: Compute final metrics: Hit Ratio = (Hits / Total) * 100 and Fault Ratio = (Faults / Total) * 100, then terminate.
2. Least Recently Used (LRU): Approximates OPT by using recent past as an indicator of near future. High overhead (counters or stack), immune to Belady's Anomaly.
3. FIFO: Replaces oldest page. Simple to implement with circular queue. Can suffer from Belady's Anomaly (more frames produce more faults).
| Feature | FIFO | LRU | Optimal (OPT) |
|---|---|---|---|
| Look Direction | Arrival Time (FIFO queue) | Past references (Backward) | Future references (Forward) |
| Page Fault Rate | Highest (typically 10-12) | Intermediate (typically 7-8) | Lowest / Optimal (typically 5-6) |
| Belady's Anomaly | YES (Subject to anomaly) | NO (Stack algorithm) | NO (Stack algorithm) |
| Hardware Support | None required | High (Counters/Stack/Age) | Theoretical only (N/A) |
| Implementation | Very Simple | Complex | Impossible in General OS |
Standard POSIX test parameters and terminal arguments.
Ans: Belady's Anomaly is the counter-intuitive phenomenon where increasing the number of physical frames increases the number of page faults. FIFO is subject to it; stack algorithms like LRU and Optimal are strictly immune.
Ans: Because it requires future knowledge of all upcoming memory page references, which cannot be known in advance in a general-purpose OS.
SOLUTION FOR PART (b) [MAX MARKS: 50]
50 MarksWrite a shell script to display the reference string and frame count used in FIFO page replacement.
To display execution parameters and configuration metadata for page replacement in Bash.
Shell: GNU Bash 5.2+ (Bourne-Again Shell) | Execution Command: chmod +x script.sh && ./script.sh
- Step 1: Start the program and declare variables for reference string, memory frames, page faults, hits, and victim frame pointer.
- Step 2: Initialize all frame slots with -1 to indicate that physical memory frames are currently empty.
- Step 3: Read each page reference from the reference string one by one in sequential arrival order.
- Step 4: Check if the incoming page is already present in any allocated frame slot. If found, mark as PAGE HIT and proceed to next reference.
- Step 5: If the page is not found (PAGE FAULT), check if free frames exist. If available, allocate the empty frame directly.
- Step 6: If all frames are occupied, apply FIFO replacement: replace the frame at index 'victim_ptr' and advance the circular pointer: victim_ptr = (victim_ptr + 1) % total_frames.
- Step 7: Increment page fault counter and display the intermediate contents of all memory frames after replacement.
- Step 8: Compute final metrics: Hit Ratio = (Hits / Total) * 100 and Fault Ratio = (Faults / Total) * 100, then terminate.
Standard POSIX test parameters and terminal arguments.
#!/bin/bash
# Shell Script: Display Reference String and Frame Parameters
ref_str=(7 0 1 2 0 3 0 4 2 3 0 3 2)
frame_count=3
echo "==============================================="
echo " FIFO PAGE REPLACEMENT PARAMETER DASHBOARD "
echo "==============================================="
echo "Total Page Requests : ${#ref_str[@]}"
echo "Frame Buffer Size : $frame_count frames"
echo "Reference Sequence : ${ref_str[*]}"
echo ""
echo "Frame Slots Initialized:"
for ((i=0; i<frame_count; i++)); do
echo " Frame Slot [$i] : [EMPTY]"
done
echo "==============================================="
Ans: Belady's Anomaly is the counter-intuitive phenomenon where increasing the number of physical frames increases the number of page faults. FIFO is subject to it; stack algorithms like LRU and Optimal are strictly immune.
Ans: Because it requires future knowledge of all upcoming memory page references, which cannot be known in advance in a general-purpose OS.